Merge pull request #21285 from mattwire/contributionviewlineitems
[civicrm-core.git] / CRM / Upgrade / Form.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
016d95d3 13 * Class CRM_Upgrade_Form
6a488035
TO
14 */
15class CRM_Upgrade_Form extends CRM_Core_Form {
7da04cde 16 const QUEUE_NAME = 'CRM_Upgrade';
6a488035
TO
17
18 /**
19 * Minimum size of MySQL's thread_stack option
20 *
21 * @see install/index.php MINIMUM_THREAD_STACK
22 */
23 const MINIMUM_THREAD_STACK = 192;
24
304c1a5a
CW
25 /**
26 * Minimum previous CiviCRM version we can directly upgrade from
27 */
fdc2e63a 28 const MINIMUM_UPGRADABLE_VERSION = '4.4.7';
304c1a5a 29
3655bea4
SL
30 /**
31 * @var \CRM_Core_Config
32 */
6a488035
TO
33 protected $_config;
34
6a488035 35 /**
fe482240 36 * Upgrade for multilingual.
6a488035 37 *
b67daa72 38 * @var bool
6a488035
TO
39 */
40 public $multilingual = FALSE;
41
42 /**
fe482240 43 * Locales available for multilingual upgrade.
6a488035
TO
44 *
45 * @var array
6a488035
TO
46 */
47 public $locales;
48
624e56fa 49 /**
fe482240 50 * Constructor for the basic form page.
624e56fa
EM
51 *
52 * We should not use QuickForm directly. This class provides a lot
53 * of default convenient functions, rules and buttons
54 *
c68f8bfa
TO
55 * @param object $state
56 * State associated with this form.
e8e8f3ad 57 * @param const|\enum|int $action The mode the form is operating in (None/Create/View/Update/Delete)
c68f8bfa
TO
58 * @param string $method
59 * The type of http method used (GET/POST).
60 * @param string $name
61 * The name of the form if different from class name.
624e56fa 62 */
ae5ffbb7 63 public function __construct(
4e66d748 64 $state = NULL,
6a488035
TO
65 $action = CRM_Core_Action::NONE,
66 $method = 'post',
e418776c 67 $name = NULL
6a488035
TO
68 ) {
69 $this->_config = CRM_Core_Config::singleton();
70
394d18d3 71 $locales = CRM_Core_I18n::getMultilingual();
6a488035 72
394d18d3
CW
73 $this->multilingual = (bool) $locales;
74 $this->locales = $locales;
6a488035
TO
75
76 $smarty = CRM_Core_Smarty::singleton();
635f0b86 77 //$smarty->compile_dir = $this->_config->templateCompileDir;
6a488035
TO
78 $smarty->assign('multilingual', $this->multilingual);
79 $smarty->assign('locales', $this->locales);
80
81 // we didn't call CRM_Core_BAO_ConfigSetting::retrieve(), so we need to set $dbLocale by hand
82 if ($this->multilingual) {
83 global $dbLocale;
84 $dbLocale = "_{$this->_config->lcMessages}";
85 }
86
87 parent::__construct($state, $action, $method, $name);
88 }
89
624e56fa 90 /**
dbdba8f2
TO
91 * @param string $version
92 * Ex: '5.22' or '5.22.3'
624e56fa 93 *
dbdba8f2
TO
94 * @return CRM_Upgrade_Incremental_Base
95 * Ex: CRM_Upgrade_Incremental_php_FiveTwentyTwo
624e56fa 96 */
00be9182 97 public static function &incrementalPhpObject($version) {
be2fb01f 98 static $incrementalPhpObject = [];
6a488035
TO
99
100 $versionParts = explode('.', $version);
0ae8b1af 101 $versionName = CRM_Utils_EnglishNumber::toCamelCase($versionParts[0]) . CRM_Utils_EnglishNumber::toCamelCase($versionParts[1]);
6a488035
TO
102
103 if (!array_key_exists($versionName, $incrementalPhpObject)) {
0e6e8724 104 $className = "CRM_Upgrade_Incremental_php_{$versionName}";
e8cb3963 105 $incrementalPhpObject[$versionName] = new $className();
6a488035
TO
106 }
107 return $incrementalPhpObject[$versionName];
108 }
109
bd6c6256
TO
110 /**
111 * @return array
112 * ex: ['5.13', '5.14', '5.15']
113 */
114 public static function incrementalPhpObjectVersions() {
115 $versions = [];
116
117 $phpDir = implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'Incremental', 'php']);
118 $phpFiles = glob("$phpDir/*.php");
119 foreach ($phpFiles as $phpFile) {
120 $phpWord = substr(basename($phpFile), 0, -4);
121 if (CRM_Utils_EnglishNumber::isNumeric($phpWord)) {
122 /** @var \CRM_Upgrade_Incremental_Base $instance */
123 $className = 'CRM_Upgrade_Incremental_php_' . $phpWord;
124 $instance = new $className();
125 $versions[] = $instance->getMajorMinor();
126 }
127 }
128
129 usort($versions, 'version_compare');
130 return $versions;
131 }
132
624e56fa
EM
133 /**
134 * @param $version
135 * @param $release
136 *
137 * @return bool
138 */
00be9182 139 public function checkVersionRelease($version, $release) {
6a488035 140 $versionParts = explode('.', $version);
bf6a5362 141 return ($versionParts[2] == $release);
6a488035
TO
142 }
143
624e56fa
EM
144 /**
145 * @param $constraints
146 *
147 * @return array
148 */
00be9182 149 public function checkSQLConstraints(&$constraints) {
6a488035
TO
150 $pass = $fail = 0;
151 foreach ($constraints as $constraint) {
152 if ($this->checkSQLConstraint($constraint)) {
153 $pass++;
154 }
155 else {
156 $fail++;
157 }
be2fb01f 158 return [$pass, $fail];
6a488035
TO
159 }
160 }
161
624e56fa
EM
162 /**
163 * @param $constraint
164 *
165 * @return bool
166 */
00be9182 167 public function checkSQLConstraint($constraint) {
6a488035
TO
168 // check constraint here
169 return TRUE;
170 }
171
624e56fa 172 /**
100fef9d 173 * @param string $fileName
624e56fa
EM
174 * @param bool $isQueryString
175 */
00be9182 176 public function source($fileName, $isQueryString = FALSE) {
c0e4c31d
JK
177 if ($isQueryString) {
178 CRM_Utils_File::runSqlQuery($this->_config->dsn,
179 $fileName, NULL
180 );
181 }
182 else {
183 CRM_Utils_File::sourceSQLFile($this->_config->dsn,
184 $fileName, NULL
185 );
186 }
6a488035
TO
187 }
188
00be9182 189 public function preProcess() {
6a488035
TO
190 CRM_Utils_System::setTitle($this->getTitle());
191 if (!$this->verifyPreDBState($errorMessage)) {
192 if (!isset($errorMessage)) {
193 $errorMessage = 'pre-condition failed for current upgrade step';
194 }
800dad43 195 throw new CRM_Core_Exception($errorMessage);
6a488035
TO
196 }
197 $this->assign('recentlyViewed', FALSE);
198 }
199
00be9182 200 public function buildQuickForm() {
6a488035
TO
201 $this->addDefaultButtons($this->getButtonTitle(),
202 'next',
203 NULL,
204 TRUE
205 );
206 }
207
624e56fa 208 /**
100fef9d 209 * Getter function for title. Should be over-ridden by derived class
624e56fa
EM
210 *
211 * @return string
624e56fa 212 */
3655bea4 213
624e56fa
EM
214 /**
215 * @return string
216 */
00be9182 217 public function getTitle() {
6a488035
TO
218 return ts('Title not Set');
219 }
220
624e56fa
EM
221 /**
222 * @return string
223 */
00be9182 224 public function getFieldsetTitle() {
ba8f6a69 225 return '';
6a488035
TO
226 }
227
624e56fa
EM
228 /**
229 * @return string
230 */
00be9182 231 public function getButtonTitle() {
6a488035
TO
232 return ts('Continue');
233 }
234
624e56fa 235 /**
fe482240 236 * Use the form name to create the tpl file name.
624e56fa
EM
237 *
238 * @return string
624e56fa 239 */
3655bea4 240
624e56fa
EM
241 /**
242 * @return string
243 */
00be9182 244 public function getTemplateFileName() {
6a488035
TO
245 $this->assign('title',
246 $this->getFieldsetTitle()
247 );
248 $this->assign('message',
249 $this->getTemplateMessage()
250 );
251 return 'CRM/Upgrade/Base.tpl';
252 }
253
00be9182 254 public function postProcess() {
6a488035
TO
255 $this->upgrade();
256
257 if (!$this->verifyPostDBState($errorMessage)) {
258 if (!isset($errorMessage)) {
259 $errorMessage = 'post-condition failed for current upgrade step';
260 }
800dad43 261 throw new CRM_Core_Exception($errorMessage);
6a488035
TO
262 }
263 }
264
624e56fa
EM
265 /**
266 * @param $query
267 *
268 * @return Object
269 */
00be9182 270 public function runQuery($query) {
e03e1641 271 return CRM_Core_DAO::executeQuery($query);
6a488035
TO
272 }
273
624e56fa
EM
274 /**
275 * @param $version
276 *
277 * @return Object
278 */
00be9182 279 public function setVersion($version) {
6a488035
TO
280 $this->logVersion($version);
281
282 $query = "
283UPDATE civicrm_domain
284SET version = '$version'
285";
286 return $this->runQuery($query);
287 }
288
624e56fa
EM
289 /**
290 * @param $newVersion
291 *
292 * @return bool
293 */
00be9182 294 public function logVersion($newVersion) {
6a488035
TO
295 if ($newVersion) {
296 $oldVersion = CRM_Core_BAO_Domain::version();
297
298 $session = CRM_Core_Session::singleton();
be2fb01f 299 $logParams = [
6a488035
TO
300 'entity_table' => 'civicrm_domain',
301 'entity_id' => 1,
302 'data' => "upgrade:{$oldVersion}->{$newVersion}",
303 // lets skip 'modified_id' for now, as it causes FK issues And
304 // is not very important for now.
305 'modified_date' => date('YmdHis'),
be2fb01f 306 ];
6a488035
TO
307 CRM_Core_BAO_Log::add($logParams);
308 return TRUE;
309 }
310
311 return FALSE;
312 }
313
624e56fa 314 /**
bd6c6256
TO
315 * Get a list of all patch-versions that appear in upgrade steps, whether
316 * as *.mysql.tpl or as *.php.
317 *
624e56fa
EM
318 * @return array
319 * @throws Exception
320 */
00be9182 321 public function getRevisionSequence() {
be2fb01f 322 $revList = [];
6a488035 323
bd6c6256
TO
324 foreach (self::incrementalPhpObjectVersions() as $majorMinor) {
325 $phpUpgrader = self::incrementalPhpObject($majorMinor);
326 $revList = array_merge($revList, array_values($phpUpgrader->getRevisionSequence()));
6a488035
TO
327 }
328
6a488035
TO
329 usort($revList, 'version_compare');
330 return $revList;
331 }
332
624e56fa
EM
333 /**
334 * @param $tplFile
335 * @param $rev
336 *
337 * @return bool
338 */
00be9182 339 public function processLocales($tplFile, $rev) {
6a488035
TO
340 $smarty = CRM_Core_Smarty::singleton();
341 $smarty->assign('domainID', CRM_Core_Config::domainID());
342
343 $this->source($smarty->fetch($tplFile), TRUE);
344
345 if ($this->multilingual) {
346 CRM_Core_I18n_Schema::rebuildMultilingualSchema($this->locales, $rev);
347 }
348 return $this->multilingual;
349 }
350
624e56fa
EM
351 /**
352 * @param $rev
353 */
00be9182 354 public function setSchemaStructureTables($rev) {
6a488035
TO
355 if ($this->multilingual) {
356 CRM_Core_I18n_Schema::schemaStructureTables($rev, TRUE);
357 }
358 }
359
624e56fa
EM
360 /**
361 * @param $rev
362 *
363 * @throws Exception
364 */
00be9182 365 public function processSQL($rev) {
6a488035 366 $sqlFile = implode(DIRECTORY_SEPARATOR,
be2fb01f 367 [
353ffa53
TO
368 dirname(__FILE__),
369 'Incremental',
370 'sql',
371 $rev . '.mysql',
be2fb01f 372 ]
6a488035
TO
373 );
374 $tplFile = "$sqlFile.tpl";
375
376 if (file_exists($tplFile)) {
377 $this->processLocales($tplFile, $rev);
378 }
379 else {
380 if (!file_exists($sqlFile)) {
800dad43 381 throw new CRM_Core_Exception("sqlfile - $rev.mysql not found.");
6a488035
TO
382 }
383 $this->source($sqlFile);
384 }
385 }
386
387 /**
fe482240 388 * Determine the start and end version of the upgrade process.
6a488035
TO
389 *
390 * @return array(0=>$currentVer, 1=>$latestVer)
391 */
00be9182 392 public function getUpgradeVersions() {
6a488035 393 $latestVer = CRM_Utils_System::version();
e418776c 394 $currentVer = CRM_Core_BAO_Domain::version(TRUE);
6a488035 395 if (!$currentVer) {
800dad43 396 throw new CRM_Core_Exception(ts('Version information missing in civicrm database.'));
6a488035
TO
397 }
398 elseif (stripos($currentVer, 'upgrade')) {
800dad43 399 throw new CRM_Core_Exception(ts('Database check failed - the database looks to have been partially upgraded. You may want to reload the database with the backup and try the upgrade process again.'));
6a488035
TO
400 }
401 if (!$latestVer) {
800dad43 402 throw new CRM_Core_Exception(ts('Version information missing in civicrm codebase.'));
6a488035
TO
403 }
404
be2fb01f 405 return [$currentVer, $latestVer];
6a488035
TO
406 }
407
408 /**
409 * Determine if $currentVer can be upgraded to $latestVer
410 *
77b97be7
EM
411 * @param $currentVer
412 * @param $latestVer
413 *
6a488035
TO
414 * @return mixed, a string error message or boolean 'false' if OK
415 */
00be9182 416 public function checkUpgradeableVersion($currentVer, $latestVer) {
6a488035
TO
417 $error = FALSE;
418 // since version is suppose to be in valid format at this point, especially after conversion ($convertVer),
419 // lets do a pattern check -
420 if (!CRM_Utils_System::isVersionFormatValid($currentVer)) {
421 $error = ts('Database is marked with invalid version format. You may want to investigate this before you proceed further.');
422 }
423 elseif (version_compare($currentVer, $latestVer) > 0) {
424 // DB version number is higher than codebase being upgraded to. This is unexpected condition-fatal error.
425 $error = ts('Your database is marked with an unexpected version number: %1. The automated upgrade to version %2 can not be run - and the %2 codebase may not be compatible with your database state. You will need to determine the correct version corresponding to your current database state. You may want to revert to the codebase you were using prior to beginning this upgrade until you resolve this problem.',
be2fb01f 426 [1 => $currentVer, 2 => $latestVer]
6a488035
TO
427 );
428 }
429 elseif (version_compare($currentVer, $latestVer) == 0) {
430 $error = ts('Your database has already been upgraded to CiviCRM %1',
be2fb01f 431 [1 => $latestVer]
6a488035
TO
432 );
433 }
304c1a5a
CW
434 elseif (version_compare($currentVer, self::MINIMUM_UPGRADABLE_VERSION) < 0) {
435 $error = ts('CiviCRM versions prior to %1 cannot be upgraded directly to %2. This upgrade will need to be done in stages. First download an intermediate version (the LTS may be a good choice) and upgrade to that before proceeding to this version.',
be2fb01f 436 [1 => self::MINIMUM_UPGRADABLE_VERSION, 2 => $latestVer]
304c1a5a
CW
437 );
438 }
6a488035 439
243b25d9 440 if (version_compare(phpversion(), CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER) < 0) {
6a488035 441 $error = ts('CiviCRM %3 requires PHP version %1 (or newer), but the current system uses %2 ',
be2fb01f 442 [
243b25d9 443 1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER,
304c1a5a 444 2 => phpversion(),
353ffa53 445 3 => $latestVer,
be2fb01f 446 ]);
6a488035
TO
447 }
448
b1634834
SL
449 if (version_compare(CRM_Utils_SQL::getDatabaseVersion(), CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER) < 0) {
450 $error = ts('CiviCRM %4 requires MySQL version v%1 or MariaDB v%3 (or newer), but the current system uses %2 ',
451 [
452 1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER,
453 2 => CRM_Utils_SQL::getDatabaseVersion(),
454 3 => '10.1',
455 4 => $latestVer,
456 ]);
457 }
458
6a488035 459 // check for mysql trigger privileges
509e50b7 460 if (!\Civi::settings()->get('logging_no_trigger_permission') && !CRM_Core_DAO::checkTriggerViewPermission(FALSE, TRUE)) {
6a488035 461 $error = ts('CiviCRM %1 requires MySQL trigger privileges.',
be2fb01f 462 [1 => $latestVer]);
6a488035 463 }
032c9d10 464
e418776c 465 if (CRM_Core_DAO::getGlobalSetting('thread_stack', 0) < (1024 * self::MINIMUM_THREAD_STACK)) {
be2fb01f 466 $error = ts('CiviCRM %1 requires MySQL thread stack >= %2k', [
6a488035 467 1 => $latestVer,
21dfd5f5 468 2 => self::MINIMUM_THREAD_STACK,
be2fb01f 469 ]);
6a488035
TO
470 }
471
472 return $error;
473 }
474
475 /**
476 * Determine if $currentver already matches $latestVer
477 *
77b97be7
EM
478 * @param $currentVer
479 * @param $latestVer
480 *
6a488035
TO
481 * @return mixed, a string error message or boolean 'false' if OK
482 */
00be9182 483 public function checkCurrentVersion($currentVer, $latestVer) {
6a488035
TO
484 $error = FALSE;
485
486 // since version is suppose to be in valid format at this point, especially after conversion ($convertVer),
487 // lets do a pattern check -
488 if (!CRM_Utils_System::isVersionFormatValid($currentVer)) {
489 $error = ts('Database is marked with invalid version format. You may want to investigate this before you proceed further.');
490 }
491 elseif (version_compare($currentVer, $latestVer) != 0) {
492 $error = ts('Your database is not configured for version %1',
be2fb01f 493 [1 => $latestVer]
6a488035
TO
494 );
495 }
496 return $error;
497 }
498
499 /**
fe482240 500 * Fill the queue with upgrade tasks.
6a488035 501 *
5a4f6742
CW
502 * @param string $currentVer
503 * the original revision.
504 * @param string $latestVer
505 * the target (final) revision.
506 * @param string $postUpgradeMessageFile
507 * path of a modifiable file which lists the post-upgrade messages.
6a488035 508 *
bf6a5362 509 * @return CRM_Queue_Service
6a488035 510 */
00be9182 511 public static function buildQueue($currentVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
512 $upgrade = new CRM_Upgrade_Form();
513
6a488035
TO
514 // Ensure that queue can be created
515 if (!CRM_Queue_BAO_QueueItem::findCreateTable()) {
800dad43 516 throw new CRM_Core_Exception(ts('Failed to find or create queueing table'));
6a488035 517 }
be2fb01f 518 $queue = CRM_Queue_Service::singleton()->create([
353ffa53
TO
519 'name' => self::QUEUE_NAME,
520 'type' => 'Sql',
521 'reset' => TRUE,
be2fb01f 522 ]);
6a488035 523
9e799b1d 524 $task = new CRM_Queue_Task(
be2fb01f
CW
525 ['CRM_Upgrade_Form', 'doFileCleanup'],
526 [$postUpgradeMessageFile],
9e799b1d
TO
527 "Cleanup old files"
528 );
529 $queue->createItem($task);
530
e4c4f267 531 $task = new CRM_Queue_Task(
be2fb01f
CW
532 ['CRM_Upgrade_Form', 'disableOldExtensions'],
533 [$postUpgradeMessageFile],
e4c4f267
CW
534 "Checking extensions"
535 );
536 $queue->createItem($task);
537
6a488035 538 $revisions = $upgrade->getRevisionSequence();
bd6c6256
TO
539 $maxRevision = empty($revisions) ? NULL : end($revisions);
540 reset($revisions);
541 if (version_compare($latestVer, $maxRevision, '<')) {
542 throw new CRM_Core_Exception("Malformed upgrade sequence. The incremental update $maxRevision exceeds target version $latestVer");
543 }
544
6a488035
TO
545 foreach ($revisions as $rev) {
546 // proceed only if $currentVer < $rev
547 if (version_compare($currentVer, $rev) < 0) {
548 $beginTask = new CRM_Queue_Task(
353ffa53 549 // callback
be2fb01f 550 ['CRM_Upgrade_Form', 'doIncrementalUpgradeStart'],
6a488035 551 // arguments
be2fb01f 552 [$rev],
6a488035
TO
553 "Begin Upgrade to $rev"
554 );
555 $queue->createItem($beginTask);
556
557 $task = new CRM_Queue_Task(
353ffa53 558 // callback
be2fb01f 559 ['CRM_Upgrade_Form', 'doIncrementalUpgradeStep'],
6a488035 560 // arguments
be2fb01f 561 [$rev, $currentVer, $latestVer, $postUpgradeMessageFile],
6a488035
TO
562 "Upgrade DB to $rev"
563 );
564 $queue->createItem($task);
565
566 $task = new CRM_Queue_Task(
353ffa53 567 // callback
be2fb01f 568 ['CRM_Upgrade_Form', 'doIncrementalUpgradeFinish'],
6a488035 569 // arguments
be2fb01f 570 [$rev, $currentVer, $latestVer, $postUpgradeMessageFile],
6a488035
TO
571 "Finish Upgrade DB to $rev"
572 );
573 $queue->createItem($task);
574 }
575 }
576
bd6c6256
TO
577 // It's possible that xml/version.xml points to a version that doesn't have any concrete revision steps.
578 if (!in_array($latestVer, $revisions)) {
579 $task = new CRM_Queue_Task(
580 ['CRM_Upgrade_Form', 'doIncrementalUpgradeFinish'],
581 [$rev, $latestVer, $latestVer, $postUpgradeMessageFile],
582 "Finish Upgrade DB to $latestVer"
583 );
584 $queue->createItem($task);
585 }
586
6a488035
TO
587 return $queue;
588 }
589
9e799b1d
TO
590 /**
591 * Find any old, orphaned files that should have been deleted.
592 *
593 * These files can get left behind, eg, if you use the Joomla
594 * upgrade procedure.
595 *
596 * The earlier we can do this, the better - don't want upgrade logic
597 * to inadvertently rely on old/relocated files.
598 *
599 * @param \CRM_Queue_TaskContext $ctx
600 * @param string $postUpgradeMessageFile
601 * @return bool
602 */
603 public static function doFileCleanup(CRM_Queue_TaskContext $ctx, $postUpgradeMessageFile) {
604 $source = new CRM_Utils_Check_Component_Source();
605 $files = $source->findOrphanedFiles();
be2fb01f 606 $errors = [];
9e799b1d
TO
607 foreach ($files as $file) {
608 if (is_dir($file['path'])) {
609 @rmdir($file['path']);
610 }
611 else {
612 @unlink($file['path']);
613 }
614
615 if (file_exists($file['path'])) {
616 $errors[] = sprintf("<li>%s</li>", htmlentities($file['path']));
617 }
618 }
619
620 if (!empty($errors)) {
621 file_put_contents($postUpgradeMessageFile,
622 '<br/><br/>' . ts('Some old files could not be removed. Please remove them.')
623 . '<ul>' . implode("\n", $errors) . '</ul>',
624 FILE_APPEND
625 );
626 }
627
628 return TRUE;
629 }
630
e4c4f267 631 /**
df7a1988 632 * Disable/uninstall any extensions not compatible with this new version.
e4c4f267
CW
633 *
634 * @param \CRM_Queue_TaskContext $ctx
635 * @param string $postUpgradeMessageFile
636 * @return bool
637 */
638 public static function disableOldExtensions(CRM_Queue_TaskContext $ctx, $postUpgradeMessageFile) {
df7a1988 639 $messages = [];
e4c4f267 640 $manager = CRM_Extension_System::singleton()->getManager();
df7a1988 641 foreach ($manager->getStatuses() as $key => $status) {
d5abe16a 642 $enableReplacement = CRM_Core_DAO::singleValueQuery('SELECT is_active FROM civicrm_extension WHERE full_name = %1', [1 => [$key, 'String']]);
df7a1988
CW
643 $obsolete = $manager->isIncompatible($key);
644 if ($obsolete) {
645 if (!empty($obsolete['disable']) && in_array($status, [$manager::STATUS_INSTALLED, $manager::STATUS_INSTALLED_MISSING])) {
646 try {
647 $manager->disable($key);
648 // Update the status for the sake of uninstall below.
649 $status = $status == $manager::STATUS_INSTALLED ? $manager::STATUS_DISABLED : $manager::STATUS_DISABLED_MISSING;
650 // This message is intentionally overwritten by uninstall below as it would be redundant
651 $messages[$key] = ts('The extension %1 is now obsolete and has been disabled.', [1 => $key]);
652 }
653 catch (CRM_Extension_Exception $e) {
654 $messages[] = ts('The obsolete extension %1 could not be removed due to an error. It is recommended to remove this extension manually.', [1 => $key]);
655 }
656 }
657 if (!empty($obsolete['uninstall']) && in_array($status, [$manager::STATUS_DISABLED, $manager::STATUS_DISABLED_MISSING])) {
658 try {
659 $manager->uninstall($key);
660 $messages[$key] = ts('The extension %1 is now obsolete and has been uninstalled.', [1 => $key]);
661 if ($status == $manager::STATUS_DISABLED) {
662 $messages[$key] .= ' ' . ts('You can remove it from your extensions directory.');
663 }
664 }
665 catch (CRM_Extension_Exception $e) {
666 $messages[] = ts('The obsolete extension %1 could not be removed due to an error. It is recommended to remove this extension manually.', [1 => $key]);
667 }
668 }
8a0199a3
TO
669 if (!empty($obsolete['force-uninstall'])) {
670 CRM_Core_DAO::executeQuery('UPDATE civicrm_extension SET is_active = 0 WHERE full_name = %1', [
671 1 => [$key, 'String'],
672 ]);
673 }
d5abe16a
SL
674 if (!empty($obsolete['replacement']) && $enableReplacement) {
675 try {
676 $manager->enable($manager->install($obsolete['replacement']));
677 $messages[] = ts('A replacement extension %1 has been installed as you had the obsolete extension %2 installed', [1 => $obsolete['replacement'], 2 => $key]);
678 }
679 catch (CRM_Extension_Exception $e) {
680 $messages[] = ts('The replacement extension %1 could not be installed due to an error. It is recommended to enable this extension manually.', [1 => $obsolete['replacement']]);
681 }
682 }
e4c4f267
CW
683 }
684 }
df7a1988 685 if ($messages) {
e4c4f267 686 file_put_contents($postUpgradeMessageFile,
df7a1988 687 '<br/><br/><ul><li>' . implode("</li>\n<li>", $messages) . '</li></ul>',
e4c4f267
CW
688 FILE_APPEND
689 );
690 }
691
692 return TRUE;
693 }
694
6a488035 695 /**
fe482240 696 * Perform an incremental version update.
6a488035 697 *
77b97be7 698 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
699 * @param string $rev
700 * the target (intermediate) revision e.g '3.2.alpha1'.
77b97be7
EM
701 *
702 * @return bool
6a488035 703 */
00be9182 704 public static function doIncrementalUpgradeStart(CRM_Queue_TaskContext $ctx, $rev) {
6a488035
TO
705 $upgrade = new CRM_Upgrade_Form();
706
707 // as soon as we start doing anything we append ".upgrade" to version.
708 // this also helps detect any partial upgrade issues
709 $upgrade->setVersion($rev . '.upgrade');
710
711 return TRUE;
712 }
713
714 /**
fe482240 715 * Perform an incremental version update.
6a488035 716 *
77b97be7 717 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
718 * @param string $rev
719 * the target (intermediate) revision e.g '3.2.alpha1'.
720 * @param string $originalVer
721 * the original revision.
722 * @param string $latestVer
723 * the target (final) revision.
724 * @param string $postUpgradeMessageFile
725 * path of a modifiable file which lists the post-upgrade messages.
77b97be7
EM
726 *
727 * @return bool
6a488035 728 */
e418776c 729 public static function doIncrementalUpgradeStep(CRM_Queue_TaskContext $ctx, $rev, $originalVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
730 $upgrade = new CRM_Upgrade_Form();
731
732 $phpFunctionName = 'upgrade_' . str_replace('.', '_', $rev);
733
bd00780f
CW
734 $versionObject = $upgrade->incrementalPhpObject($rev);
735
736 // pre-db check for major release.
737 if ($upgrade->checkVersionRelease($rev, 'alpha1')) {
be2fb01f 738 if (!(is_callable([$versionObject, 'verifyPreDBstate']))) {
800dad43 739 throw new CRM_Core_Exception("verifyPreDBstate method was not found for $rev");
6a488035 740 }
6a488035 741
bd00780f
CW
742 $error = NULL;
743 if (!($versionObject->verifyPreDBstate($error))) {
744 if (!isset($error)) {
745 $error = "post-condition failed for current upgrade for $rev";
6a488035 746 }
800dad43 747 throw new CRM_Core_Exception($error);
6a488035
TO
748 }
749
bd00780f 750 }
6a488035 751
bd00780f 752 $upgrade->setSchemaStructureTables($rev);
6a488035 753
be2fb01f 754 if (is_callable([$versionObject, $phpFunctionName])) {
bd00780f
CW
755 $versionObject->$phpFunctionName($rev, $originalVer, $latestVer);
756 }
757 else {
758 $upgrade->processSQL($rev);
759 }
760
761 // set post-upgrade-message if any
be2fb01f 762 if (is_callable([$versionObject, 'setPostUpgradeMessage'])) {
bd00780f
CW
763 $postUpgradeMessage = file_get_contents($postUpgradeMessageFile);
764 $versionObject->setPostUpgradeMessage($postUpgradeMessage, $rev);
bd00780f 765 file_put_contents($postUpgradeMessageFile, $postUpgradeMessage);
6a488035
TO
766 }
767
768 return TRUE;
769 }
770
771 /**
bd6c6256
TO
772 * Mark an incremental update as finished.
773 *
774 * This method may be called in two cases:
775 *
776 * - After performing each incremental update (`X.X.X.mysql.tpl` or `upgrade_X_X_X()`)
777 * - If needed, one more time at the end of the upgrade for the final version-number.
6a488035 778 *
77b97be7 779 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
780 * @param string $rev
781 * the target (intermediate) revision e.g '3.2.alpha1'.
782 * @param string $currentVer
783 * the original revision.
784 * @param string $latestVer
785 * the target (final) revision.
786 * @param string $postUpgradeMessageFile
787 * path of a modifiable file which lists the post-upgrade messages.
77b97be7
EM
788 *
789 * @return bool
6a488035 790 */
00be9182 791 public static function doIncrementalUpgradeFinish(CRM_Queue_TaskContext $ctx, $rev, $currentVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
792 $upgrade = new CRM_Upgrade_Form();
793 $upgrade->setVersion($rev);
794 CRM_Utils_System::flushCache();
ac05cde3 795
6a488035
TO
796 return TRUE;
797 }
798
00be9182 799 public static function doFinish() {
ecb0ae5d
TO
800 Civi::dispatcher()->setDispatchPolicy(\CRM_Upgrade_DispatchPolicy::get('upgrade.finish'));
801 $restore = \CRM_Utils_AutoClean::with(function() {
802 Civi::dispatcher()->setDispatchPolicy(\CRM_Upgrade_DispatchPolicy::get('upgrade.main'));
803 });
804
6a488035
TO
805 $upgrade = new CRM_Upgrade_Form();
806 list($ignore, $latestVer) = $upgrade->getUpgradeVersions();
807 // Seems extraneous in context, but we'll preserve old behavior
808 $upgrade->setVersion($latestVer);
809
f3209f6d
TO
810 $config = CRM_Core_Config::singleton();
811 $config->userSystem->flush();
812
3af5c3fe
TO
813 CRM_Core_Invoke::rebuildMenuAndCaches(FALSE, TRUE);
814 // NOTE: triggerRebuild is FALSE becaues it will run again in a moment (via fixSchemaDifferences).
6a488035 815
6b4bec74
CW
816 $versionCheck = new CRM_Utils_VersionCheck();
817 $versionCheck->flushCache();
818
6a488035
TO
819 // Rebuild all triggers and re-enable logging if needed
820 $logging = new CRM_Logging_Schema();
821 $logging->fixSchemaDifferences();
756d9e0d
SL
822 // Force a rebuild of CiviCRM asset cache in case things have changed.
823 \Civi::service('asset_builder')->clear(FALSE);
6a488035
TO
824 }
825
826 /**
827 * Compute any messages which should be displayed before upgrade
828 * by calling the 'setPreUpgradeMessage' on each incremental upgrade
829 * object.
830 *
5a4f6742
CW
831 * @param string $preUpgradeMessage
832 * alterable.
77b97be7
EM
833 * @param $currentVer
834 * @param $latestVer
6a488035 835 */
00be9182 836 public function setPreUpgradeMessage(&$preUpgradeMessage, $currentVer, $latestVer) {
49368097
CW
837 // check for changed message templates
838 CRM_Upgrade_Incremental_General::checkMessageTemplate($preUpgradeMessage, $latestVer, $currentVer);
839 // set global messages
840 CRM_Upgrade_Incremental_General::setPreUpgradeMessage($preUpgradeMessage, $currentVer, $latestVer);
6a488035
TO
841
842 // Scan through all php files and see if any file is interested in setting pre-upgrade-message
843 // based on $currentVer, $latestVer.
844 // Please note, at this point upgrade hasn't started executing queries.
845 $revisions = $this->getRevisionSequence();
846 foreach ($revisions as $rev) {
49368097 847 if (version_compare($currentVer, $rev) < 0) {
6a488035 848 $versionObject = $this->incrementalPhpObject($rev);
fe83c251 849 CRM_Upgrade_Incremental_General::updateMessageTemplate($preUpgradeMessage, $rev);
be2fb01f 850 if (is_callable([$versionObject, 'setPreUpgradeMessage'])) {
e418776c
TO
851 $versionObject->setPreUpgradeMessage($preUpgradeMessage, $rev, $currentVer);
852 }
6a488035
TO
853 }
854 }
855 }
96025800 856
6a488035 857}