Merge pull request #23213 from eileenmcnaughton/post
[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 */
d9f8d639 28 const MINIMUM_UPGRADABLE_VERSION = '4.6.12';
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() {
203b4734 190 $this->setTitle($this->getTitle());
6a488035
TO
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 *
c09c9d3d
TO
502 * The queue is a priority-queue (sorted by tuple weight+id). Here are some common weights:
503 *
504 * - `weight=0`: Add a typical upgrade step for revising core schema.
505 * - `weight=-1`: In the middle of the upgrade, add an extra step for immediate execution.
506 * - `weight=1000`: Add some general core upgrade-logic that runs after all schema have change.d
507 * - `weight=2000`: Add some post-upgrade logic. If a task absolutely requires full system services
508 * (eg enabling a new extension), then place it here.
509 *
5a4f6742
CW
510 * @param string $currentVer
511 * the original revision.
512 * @param string $latestVer
513 * the target (final) revision.
514 * @param string $postUpgradeMessageFile
515 * path of a modifiable file which lists the post-upgrade messages.
6a488035 516 *
bf6a5362 517 * @return CRM_Queue_Service
6a488035 518 */
00be9182 519 public static function buildQueue($currentVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
520 $upgrade = new CRM_Upgrade_Form();
521
6a488035
TO
522 // Ensure that queue can be created
523 if (!CRM_Queue_BAO_QueueItem::findCreateTable()) {
800dad43 524 throw new CRM_Core_Exception(ts('Failed to find or create queueing table'));
6a488035 525 }
be2fb01f 526 $queue = CRM_Queue_Service::singleton()->create([
353ffa53
TO
527 'name' => self::QUEUE_NAME,
528 'type' => 'Sql',
529 'reset' => TRUE,
be2fb01f 530 ]);
6a488035 531
9e799b1d 532 $task = new CRM_Queue_Task(
be2fb01f
CW
533 ['CRM_Upgrade_Form', 'doFileCleanup'],
534 [$postUpgradeMessageFile],
9e799b1d
TO
535 "Cleanup old files"
536 );
c09c9d3d 537 $queue->createItem($task, ['weight' => 0]);
9e799b1d 538
e4c4f267 539 $task = new CRM_Queue_Task(
be2fb01f
CW
540 ['CRM_Upgrade_Form', 'disableOldExtensions'],
541 [$postUpgradeMessageFile],
e4c4f267
CW
542 "Checking extensions"
543 );
c09c9d3d 544 $queue->createItem($task, ['weight' => 0]);
e4c4f267 545
6a488035 546 $revisions = $upgrade->getRevisionSequence();
bd6c6256
TO
547 $maxRevision = empty($revisions) ? NULL : end($revisions);
548 reset($revisions);
549 if (version_compare($latestVer, $maxRevision, '<')) {
550 throw new CRM_Core_Exception("Malformed upgrade sequence. The incremental update $maxRevision exceeds target version $latestVer");
551 }
552
6a488035
TO
553 foreach ($revisions as $rev) {
554 // proceed only if $currentVer < $rev
555 if (version_compare($currentVer, $rev) < 0) {
556 $beginTask = new CRM_Queue_Task(
353ffa53 557 // callback
be2fb01f 558 ['CRM_Upgrade_Form', 'doIncrementalUpgradeStart'],
6a488035 559 // arguments
be2fb01f 560 [$rev],
6a488035
TO
561 "Begin Upgrade to $rev"
562 );
c09c9d3d 563 $queue->createItem($beginTask, ['weight' => 0]);
6a488035
TO
564
565 $task = new CRM_Queue_Task(
353ffa53 566 // callback
be2fb01f 567 ['CRM_Upgrade_Form', 'doIncrementalUpgradeStep'],
6a488035 568 // arguments
be2fb01f 569 [$rev, $currentVer, $latestVer, $postUpgradeMessageFile],
6a488035
TO
570 "Upgrade DB to $rev"
571 );
c09c9d3d 572 $queue->createItem($task, ['weight' => 0]);
6a488035
TO
573
574 $task = new CRM_Queue_Task(
353ffa53 575 // callback
be2fb01f 576 ['CRM_Upgrade_Form', 'doIncrementalUpgradeFinish'],
6a488035 577 // arguments
be2fb01f 578 [$rev, $currentVer, $latestVer, $postUpgradeMessageFile],
6a488035
TO
579 "Finish Upgrade DB to $rev"
580 );
c09c9d3d 581 $queue->createItem($task, ['weight' => 0]);
6a488035
TO
582 }
583 }
584
bd6c6256
TO
585 // It's possible that xml/version.xml points to a version that doesn't have any concrete revision steps.
586 if (!in_array($latestVer, $revisions)) {
587 $task = new CRM_Queue_Task(
588 ['CRM_Upgrade_Form', 'doIncrementalUpgradeFinish'],
589 [$rev, $latestVer, $latestVer, $postUpgradeMessageFile],
590 "Finish Upgrade DB to $latestVer"
591 );
c09c9d3d 592 $queue->createItem($task, ['weight' => 0]);
bd6c6256
TO
593 }
594
c09c9d3d
TO
595 $task = new CRM_Queue_Task(
596 ['CRM_Upgrade_Form', 'doCoreFinish'],
597 [$rev, $latestVer, $latestVer, $postUpgradeMessageFile],
598 "Finish core DB updates $latestVer"
599 );
600 $queue->createItem($task, ['weight' => 1000]);
601
6a488035
TO
602 return $queue;
603 }
604
9e799b1d
TO
605 /**
606 * Find any old, orphaned files that should have been deleted.
607 *
608 * These files can get left behind, eg, if you use the Joomla
609 * upgrade procedure.
610 *
611 * The earlier we can do this, the better - don't want upgrade logic
612 * to inadvertently rely on old/relocated files.
613 *
614 * @param \CRM_Queue_TaskContext $ctx
615 * @param string $postUpgradeMessageFile
616 * @return bool
617 */
618 public static function doFileCleanup(CRM_Queue_TaskContext $ctx, $postUpgradeMessageFile) {
619 $source = new CRM_Utils_Check_Component_Source();
620 $files = $source->findOrphanedFiles();
be2fb01f 621 $errors = [];
9e799b1d
TO
622 foreach ($files as $file) {
623 if (is_dir($file['path'])) {
624 @rmdir($file['path']);
625 }
626 else {
627 @unlink($file['path']);
628 }
629
630 if (file_exists($file['path'])) {
631 $errors[] = sprintf("<li>%s</li>", htmlentities($file['path']));
632 }
633 }
634
635 if (!empty($errors)) {
636 file_put_contents($postUpgradeMessageFile,
637 '<br/><br/>' . ts('Some old files could not be removed. Please remove them.')
638 . '<ul>' . implode("\n", $errors) . '</ul>',
639 FILE_APPEND
640 );
641 }
642
643 return TRUE;
644 }
645
e4c4f267 646 /**
df7a1988 647 * Disable/uninstall any extensions not compatible with this new version.
e4c4f267
CW
648 *
649 * @param \CRM_Queue_TaskContext $ctx
650 * @param string $postUpgradeMessageFile
651 * @return bool
652 */
653 public static function disableOldExtensions(CRM_Queue_TaskContext $ctx, $postUpgradeMessageFile) {
df7a1988 654 $messages = [];
e4c4f267 655 $manager = CRM_Extension_System::singleton()->getManager();
df7a1988 656 foreach ($manager->getStatuses() as $key => $status) {
d5abe16a 657 $enableReplacement = CRM_Core_DAO::singleValueQuery('SELECT is_active FROM civicrm_extension WHERE full_name = %1', [1 => [$key, 'String']]);
df7a1988
CW
658 $obsolete = $manager->isIncompatible($key);
659 if ($obsolete) {
660 if (!empty($obsolete['disable']) && in_array($status, [$manager::STATUS_INSTALLED, $manager::STATUS_INSTALLED_MISSING])) {
661 try {
662 $manager->disable($key);
663 // Update the status for the sake of uninstall below.
664 $status = $status == $manager::STATUS_INSTALLED ? $manager::STATUS_DISABLED : $manager::STATUS_DISABLED_MISSING;
665 // This message is intentionally overwritten by uninstall below as it would be redundant
666 $messages[$key] = ts('The extension %1 is now obsolete and has been disabled.', [1 => $key]);
667 }
668 catch (CRM_Extension_Exception $e) {
669 $messages[] = ts('The obsolete extension %1 could not be removed due to an error. It is recommended to remove this extension manually.', [1 => $key]);
670 }
671 }
672 if (!empty($obsolete['uninstall']) && in_array($status, [$manager::STATUS_DISABLED, $manager::STATUS_DISABLED_MISSING])) {
673 try {
674 $manager->uninstall($key);
675 $messages[$key] = ts('The extension %1 is now obsolete and has been uninstalled.', [1 => $key]);
676 if ($status == $manager::STATUS_DISABLED) {
677 $messages[$key] .= ' ' . ts('You can remove it from your extensions directory.');
678 }
679 }
680 catch (CRM_Extension_Exception $e) {
681 $messages[] = ts('The obsolete extension %1 could not be removed due to an error. It is recommended to remove this extension manually.', [1 => $key]);
682 }
683 }
8a0199a3
TO
684 if (!empty($obsolete['force-uninstall'])) {
685 CRM_Core_DAO::executeQuery('UPDATE civicrm_extension SET is_active = 0 WHERE full_name = %1', [
686 1 => [$key, 'String'],
687 ]);
688 }
d5abe16a
SL
689 if (!empty($obsolete['replacement']) && $enableReplacement) {
690 try {
691 $manager->enable($manager->install($obsolete['replacement']));
692 $messages[] = ts('A replacement extension %1 has been installed as you had the obsolete extension %2 installed', [1 => $obsolete['replacement'], 2 => $key]);
693 }
694 catch (CRM_Extension_Exception $e) {
695 $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']]);
696 }
697 }
e4c4f267
CW
698 }
699 }
df7a1988 700 if ($messages) {
e4c4f267 701 file_put_contents($postUpgradeMessageFile,
df7a1988 702 '<br/><br/><ul><li>' . implode("</li>\n<li>", $messages) . '</li></ul>',
e4c4f267
CW
703 FILE_APPEND
704 );
705 }
706
707 return TRUE;
708 }
709
6a488035 710 /**
fe482240 711 * Perform an incremental version update.
6a488035 712 *
77b97be7 713 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
714 * @param string $rev
715 * the target (intermediate) revision e.g '3.2.alpha1'.
77b97be7
EM
716 *
717 * @return bool
6a488035 718 */
00be9182 719 public static function doIncrementalUpgradeStart(CRM_Queue_TaskContext $ctx, $rev) {
6a488035
TO
720 $upgrade = new CRM_Upgrade_Form();
721
722 // as soon as we start doing anything we append ".upgrade" to version.
723 // this also helps detect any partial upgrade issues
724 $upgrade->setVersion($rev . '.upgrade');
725
726 return TRUE;
727 }
728
729 /**
fe482240 730 * Perform an incremental version update.
6a488035 731 *
77b97be7 732 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
733 * @param string $rev
734 * the target (intermediate) revision e.g '3.2.alpha1'.
735 * @param string $originalVer
736 * the original revision.
737 * @param string $latestVer
738 * the target (final) revision.
739 * @param string $postUpgradeMessageFile
740 * path of a modifiable file which lists the post-upgrade messages.
77b97be7
EM
741 *
742 * @return bool
6a488035 743 */
e418776c 744 public static function doIncrementalUpgradeStep(CRM_Queue_TaskContext $ctx, $rev, $originalVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
745 $upgrade = new CRM_Upgrade_Form();
746
747 $phpFunctionName = 'upgrade_' . str_replace('.', '_', $rev);
748
bd00780f
CW
749 $versionObject = $upgrade->incrementalPhpObject($rev);
750
bd00780f 751 $upgrade->setSchemaStructureTables($rev);
6a488035 752
be2fb01f 753 if (is_callable([$versionObject, $phpFunctionName])) {
bd00780f
CW
754 $versionObject->$phpFunctionName($rev, $originalVer, $latestVer);
755 }
756 else {
b871c758 757 $ctx->log->info("Upgrade DB to $rev: SQL");
bd00780f
CW
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
c09c9d3d
TO
799 /**
800 * Finalize the core upgrade.
801 *
802 * @return bool
803 * @throws \CRM_Core_Exception
804 */
805 public static function doCoreFinish(): bool {
ecb0ae5d
TO
806 Civi::dispatcher()->setDispatchPolicy(\CRM_Upgrade_DispatchPolicy::get('upgrade.finish'));
807 $restore = \CRM_Utils_AutoClean::with(function() {
808 Civi::dispatcher()->setDispatchPolicy(\CRM_Upgrade_DispatchPolicy::get('upgrade.main'));
809 });
810
6a488035
TO
811 $upgrade = new CRM_Upgrade_Form();
812 list($ignore, $latestVer) = $upgrade->getUpgradeVersions();
813 // Seems extraneous in context, but we'll preserve old behavior
814 $upgrade->setVersion($latestVer);
815
f3209f6d
TO
816 $config = CRM_Core_Config::singleton();
817 $config->userSystem->flush();
818
b00d6b3f 819 CRM_Core_Invoke::rebuildMenuAndCaches(FALSE, FALSE);
3af5c3fe 820 // NOTE: triggerRebuild is FALSE becaues it will run again in a moment (via fixSchemaDifferences).
b00d6b3f 821 // sessionReset is FALSE because upgrade status/postUpgradeMessages are needed by the Page. We reset later in doFinish().
6a488035 822
6b4bec74
CW
823 $versionCheck = new CRM_Utils_VersionCheck();
824 $versionCheck->flushCache();
825
6a488035
TO
826 // Rebuild all triggers and re-enable logging if needed
827 $logging = new CRM_Logging_Schema();
828 $logging->fixSchemaDifferences();
756d9e0d
SL
829 // Force a rebuild of CiviCRM asset cache in case things have changed.
830 \Civi::service('asset_builder')->clear(FALSE);
c09c9d3d
TO
831
832 return TRUE;
833 }
834
835 /**
836 * After finishing the queue, the upgrade-runner calls `doFinish()`.
837 *
838 * This is called by all upgrade-runners (inside or outside of `civicrm-core.git`).
839 * Removing it would be a breaky-annoying process; it would foreclose future use;
840 * and it would produce no tangible benefits.
841 *
842 * @return bool
843 */
844 public static function doFinish(): bool {
efea3f50
TO
845 $session = CRM_Core_Session::singleton();
846 $session->reset(2);
c09c9d3d 847 return TRUE;
6a488035
TO
848 }
849
850 /**
851 * Compute any messages which should be displayed before upgrade
852 * by calling the 'setPreUpgradeMessage' on each incremental upgrade
853 * object.
854 *
5a4f6742
CW
855 * @param string $preUpgradeMessage
856 * alterable.
77b97be7
EM
857 * @param $currentVer
858 * @param $latestVer
6a488035 859 */
00be9182 860 public function setPreUpgradeMessage(&$preUpgradeMessage, $currentVer, $latestVer) {
49368097
CW
861 // check for changed message templates
862 CRM_Upgrade_Incremental_General::checkMessageTemplate($preUpgradeMessage, $latestVer, $currentVer);
863 // set global messages
864 CRM_Upgrade_Incremental_General::setPreUpgradeMessage($preUpgradeMessage, $currentVer, $latestVer);
6a488035
TO
865
866 // Scan through all php files and see if any file is interested in setting pre-upgrade-message
867 // based on $currentVer, $latestVer.
868 // Please note, at this point upgrade hasn't started executing queries.
869 $revisions = $this->getRevisionSequence();
870 foreach ($revisions as $rev) {
49368097 871 if (version_compare($currentVer, $rev) < 0) {
6a488035 872 $versionObject = $this->incrementalPhpObject($rev);
fe83c251 873 CRM_Upgrade_Incremental_General::updateMessageTemplate($preUpgradeMessage, $rev);
be2fb01f 874 if (is_callable([$versionObject, 'setPreUpgradeMessage'])) {
e418776c
TO
875 $versionObject->setPreUpgradeMessage($preUpgradeMessage, $rev, $currentVer);
876 }
6a488035
TO
877 }
878 }
879 }
96025800 880
6a488035 881}