Merge pull request #23331 from colemanw/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 $version
267 *
268 * @return Object
269 */
00be9182 270 public function setVersion($version) {
6a488035
TO
271 $this->logVersion($version);
272
273 $query = "
274UPDATE civicrm_domain
275SET version = '$version'
276";
175d734d 277 return CRM_Core_DAO::executeQuery($query);
6a488035
TO
278 }
279
624e56fa
EM
280 /**
281 * @param $newVersion
282 *
283 * @return bool
284 */
00be9182 285 public function logVersion($newVersion) {
6a488035
TO
286 if ($newVersion) {
287 $oldVersion = CRM_Core_BAO_Domain::version();
288
289 $session = CRM_Core_Session::singleton();
be2fb01f 290 $logParams = [
6a488035
TO
291 'entity_table' => 'civicrm_domain',
292 'entity_id' => 1,
293 'data' => "upgrade:{$oldVersion}->{$newVersion}",
294 // lets skip 'modified_id' for now, as it causes FK issues And
295 // is not very important for now.
296 'modified_date' => date('YmdHis'),
be2fb01f 297 ];
6a488035
TO
298 CRM_Core_BAO_Log::add($logParams);
299 return TRUE;
300 }
301
302 return FALSE;
303 }
304
624e56fa 305 /**
bd6c6256
TO
306 * Get a list of all patch-versions that appear in upgrade steps, whether
307 * as *.mysql.tpl or as *.php.
308 *
624e56fa
EM
309 * @return array
310 * @throws Exception
311 */
00be9182 312 public function getRevisionSequence() {
be2fb01f 313 $revList = [];
6a488035 314
bd6c6256
TO
315 foreach (self::incrementalPhpObjectVersions() as $majorMinor) {
316 $phpUpgrader = self::incrementalPhpObject($majorMinor);
317 $revList = array_merge($revList, array_values($phpUpgrader->getRevisionSequence()));
6a488035
TO
318 }
319
6a488035
TO
320 usort($revList, 'version_compare');
321 return $revList;
322 }
323
624e56fa
EM
324 /**
325 * @param $tplFile
326 * @param $rev
327 *
328 * @return bool
329 */
00be9182 330 public function processLocales($tplFile, $rev) {
6a488035
TO
331 $smarty = CRM_Core_Smarty::singleton();
332 $smarty->assign('domainID', CRM_Core_Config::domainID());
333
334 $this->source($smarty->fetch($tplFile), TRUE);
335
336 if ($this->multilingual) {
337 CRM_Core_I18n_Schema::rebuildMultilingualSchema($this->locales, $rev);
338 }
339 return $this->multilingual;
340 }
341
624e56fa
EM
342 /**
343 * @param $rev
344 */
00be9182 345 public function setSchemaStructureTables($rev) {
6a488035
TO
346 if ($this->multilingual) {
347 CRM_Core_I18n_Schema::schemaStructureTables($rev, TRUE);
348 }
349 }
350
624e56fa
EM
351 /**
352 * @param $rev
353 *
354 * @throws Exception
355 */
00be9182 356 public function processSQL($rev) {
6a488035 357 $sqlFile = implode(DIRECTORY_SEPARATOR,
be2fb01f 358 [
353ffa53
TO
359 dirname(__FILE__),
360 'Incremental',
361 'sql',
362 $rev . '.mysql',
be2fb01f 363 ]
6a488035
TO
364 );
365 $tplFile = "$sqlFile.tpl";
366
367 if (file_exists($tplFile)) {
368 $this->processLocales($tplFile, $rev);
369 }
370 else {
371 if (!file_exists($sqlFile)) {
800dad43 372 throw new CRM_Core_Exception("sqlfile - $rev.mysql not found.");
6a488035
TO
373 }
374 $this->source($sqlFile);
375 }
376 }
377
378 /**
fe482240 379 * Determine the start and end version of the upgrade process.
6a488035
TO
380 *
381 * @return array(0=>$currentVer, 1=>$latestVer)
382 */
00be9182 383 public function getUpgradeVersions() {
6a488035 384 $latestVer = CRM_Utils_System::version();
e418776c 385 $currentVer = CRM_Core_BAO_Domain::version(TRUE);
6a488035 386 if (!$currentVer) {
800dad43 387 throw new CRM_Core_Exception(ts('Version information missing in civicrm database.'));
6a488035
TO
388 }
389 elseif (stripos($currentVer, 'upgrade')) {
800dad43 390 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
391 }
392 if (!$latestVer) {
800dad43 393 throw new CRM_Core_Exception(ts('Version information missing in civicrm codebase.'));
6a488035
TO
394 }
395
be2fb01f 396 return [$currentVer, $latestVer];
6a488035
TO
397 }
398
399 /**
400 * Determine if $currentVer can be upgraded to $latestVer
401 *
77b97be7
EM
402 * @param $currentVer
403 * @param $latestVer
404 *
6a488035
TO
405 * @return mixed, a string error message or boolean 'false' if OK
406 */
00be9182 407 public function checkUpgradeableVersion($currentVer, $latestVer) {
6a488035
TO
408 $error = FALSE;
409 // since version is suppose to be in valid format at this point, especially after conversion ($convertVer),
410 // lets do a pattern check -
411 if (!CRM_Utils_System::isVersionFormatValid($currentVer)) {
412 $error = ts('Database is marked with invalid version format. You may want to investigate this before you proceed further.');
413 }
414 elseif (version_compare($currentVer, $latestVer) > 0) {
415 // DB version number is higher than codebase being upgraded to. This is unexpected condition-fatal error.
416 $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 417 [1 => $currentVer, 2 => $latestVer]
6a488035
TO
418 );
419 }
420 elseif (version_compare($currentVer, $latestVer) == 0) {
421 $error = ts('Your database has already been upgraded to CiviCRM %1',
be2fb01f 422 [1 => $latestVer]
6a488035
TO
423 );
424 }
304c1a5a
CW
425 elseif (version_compare($currentVer, self::MINIMUM_UPGRADABLE_VERSION) < 0) {
426 $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 427 [1 => self::MINIMUM_UPGRADABLE_VERSION, 2 => $latestVer]
304c1a5a
CW
428 );
429 }
6a488035 430
243b25d9 431 if (version_compare(phpversion(), CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER) < 0) {
6a488035 432 $error = ts('CiviCRM %3 requires PHP version %1 (or newer), but the current system uses %2 ',
be2fb01f 433 [
243b25d9 434 1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER,
304c1a5a 435 2 => phpversion(),
353ffa53 436 3 => $latestVer,
be2fb01f 437 ]);
6a488035
TO
438 }
439
b1634834
SL
440 if (version_compare(CRM_Utils_SQL::getDatabaseVersion(), CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER) < 0) {
441 $error = ts('CiviCRM %4 requires MySQL version v%1 or MariaDB v%3 (or newer), but the current system uses %2 ',
442 [
443 1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER,
444 2 => CRM_Utils_SQL::getDatabaseVersion(),
445 3 => '10.1',
446 4 => $latestVer,
447 ]);
448 }
449
6a488035 450 // check for mysql trigger privileges
509e50b7 451 if (!\Civi::settings()->get('logging_no_trigger_permission') && !CRM_Core_DAO::checkTriggerViewPermission(FALSE, TRUE)) {
6a488035 452 $error = ts('CiviCRM %1 requires MySQL trigger privileges.',
be2fb01f 453 [1 => $latestVer]);
6a488035 454 }
032c9d10 455
e418776c 456 if (CRM_Core_DAO::getGlobalSetting('thread_stack', 0) < (1024 * self::MINIMUM_THREAD_STACK)) {
be2fb01f 457 $error = ts('CiviCRM %1 requires MySQL thread stack >= %2k', [
6a488035 458 1 => $latestVer,
21dfd5f5 459 2 => self::MINIMUM_THREAD_STACK,
be2fb01f 460 ]);
6a488035
TO
461 }
462
463 return $error;
464 }
465
466 /**
467 * Determine if $currentver already matches $latestVer
468 *
77b97be7
EM
469 * @param $currentVer
470 * @param $latestVer
471 *
6a488035
TO
472 * @return mixed, a string error message or boolean 'false' if OK
473 */
00be9182 474 public function checkCurrentVersion($currentVer, $latestVer) {
6a488035
TO
475 $error = FALSE;
476
477 // since version is suppose to be in valid format at this point, especially after conversion ($convertVer),
478 // lets do a pattern check -
479 if (!CRM_Utils_System::isVersionFormatValid($currentVer)) {
480 $error = ts('Database is marked with invalid version format. You may want to investigate this before you proceed further.');
481 }
482 elseif (version_compare($currentVer, $latestVer) != 0) {
483 $error = ts('Your database is not configured for version %1',
be2fb01f 484 [1 => $latestVer]
6a488035
TO
485 );
486 }
487 return $error;
488 }
489
490 /**
fe482240 491 * Fill the queue with upgrade tasks.
6a488035 492 *
c09c9d3d
TO
493 * The queue is a priority-queue (sorted by tuple weight+id). Here are some common weights:
494 *
495 * - `weight=0`: Add a typical upgrade step for revising core schema.
496 * - `weight=-1`: In the middle of the upgrade, add an extra step for immediate execution.
497 * - `weight=1000`: Add some general core upgrade-logic that runs after all schema have change.d
498 * - `weight=2000`: Add some post-upgrade logic. If a task absolutely requires full system services
499 * (eg enabling a new extension), then place it here.
500 *
5a4f6742
CW
501 * @param string $currentVer
502 * the original revision.
503 * @param string $latestVer
504 * the target (final) revision.
505 * @param string $postUpgradeMessageFile
506 * path of a modifiable file which lists the post-upgrade messages.
6a488035 507 *
bf6a5362 508 * @return CRM_Queue_Service
6a488035 509 */
00be9182 510 public static function buildQueue($currentVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
511 $upgrade = new CRM_Upgrade_Form();
512
6a488035
TO
513 // Ensure that queue can be created
514 if (!CRM_Queue_BAO_QueueItem::findCreateTable()) {
800dad43 515 throw new CRM_Core_Exception(ts('Failed to find or create queueing table'));
6a488035 516 }
be2fb01f 517 $queue = CRM_Queue_Service::singleton()->create([
353ffa53
TO
518 'name' => self::QUEUE_NAME,
519 'type' => 'Sql',
520 'reset' => TRUE,
be2fb01f 521 ]);
6a488035 522
9e799b1d 523 $task = new CRM_Queue_Task(
be2fb01f
CW
524 ['CRM_Upgrade_Form', 'doFileCleanup'],
525 [$postUpgradeMessageFile],
9e799b1d
TO
526 "Cleanup old files"
527 );
c09c9d3d 528 $queue->createItem($task, ['weight' => 0]);
9e799b1d 529
e4c4f267 530 $task = new CRM_Queue_Task(
be2fb01f
CW
531 ['CRM_Upgrade_Form', 'disableOldExtensions'],
532 [$postUpgradeMessageFile],
e4c4f267
CW
533 "Checking extensions"
534 );
c09c9d3d 535 $queue->createItem($task, ['weight' => 0]);
e4c4f267 536
6a488035 537 $revisions = $upgrade->getRevisionSequence();
bd6c6256
TO
538 $maxRevision = empty($revisions) ? NULL : end($revisions);
539 reset($revisions);
540 if (version_compare($latestVer, $maxRevision, '<')) {
541 throw new CRM_Core_Exception("Malformed upgrade sequence. The incremental update $maxRevision exceeds target version $latestVer");
542 }
543
6a488035
TO
544 foreach ($revisions as $rev) {
545 // proceed only if $currentVer < $rev
546 if (version_compare($currentVer, $rev) < 0) {
547 $beginTask = new CRM_Queue_Task(
353ffa53 548 // callback
be2fb01f 549 ['CRM_Upgrade_Form', 'doIncrementalUpgradeStart'],
6a488035 550 // arguments
be2fb01f 551 [$rev],
6a488035
TO
552 "Begin Upgrade to $rev"
553 );
c09c9d3d 554 $queue->createItem($beginTask, ['weight' => 0]);
6a488035
TO
555
556 $task = new CRM_Queue_Task(
353ffa53 557 // callback
be2fb01f 558 ['CRM_Upgrade_Form', 'doIncrementalUpgradeStep'],
6a488035 559 // arguments
be2fb01f 560 [$rev, $currentVer, $latestVer, $postUpgradeMessageFile],
6a488035
TO
561 "Upgrade DB to $rev"
562 );
c09c9d3d 563 $queue->createItem($task, ['weight' => 0]);
6a488035
TO
564
565 $task = new CRM_Queue_Task(
353ffa53 566 // callback
be2fb01f 567 ['CRM_Upgrade_Form', 'doIncrementalUpgradeFinish'],
6a488035 568 // arguments
be2fb01f 569 [$rev, $currentVer, $latestVer, $postUpgradeMessageFile],
6a488035
TO
570 "Finish Upgrade DB to $rev"
571 );
c09c9d3d 572 $queue->createItem($task, ['weight' => 0]);
6a488035
TO
573 }
574 }
575
bd6c6256
TO
576 // It's possible that xml/version.xml points to a version that doesn't have any concrete revision steps.
577 if (!in_array($latestVer, $revisions)) {
578 $task = new CRM_Queue_Task(
579 ['CRM_Upgrade_Form', 'doIncrementalUpgradeFinish'],
580 [$rev, $latestVer, $latestVer, $postUpgradeMessageFile],
581 "Finish Upgrade DB to $latestVer"
582 );
c09c9d3d 583 $queue->createItem($task, ['weight' => 0]);
bd6c6256
TO
584 }
585
c09c9d3d
TO
586 $task = new CRM_Queue_Task(
587 ['CRM_Upgrade_Form', 'doCoreFinish'],
588 [$rev, $latestVer, $latestVer, $postUpgradeMessageFile],
589 "Finish core DB updates $latestVer"
590 );
591 $queue->createItem($task, ['weight' => 1000]);
592
6a488035
TO
593 return $queue;
594 }
595
9e799b1d
TO
596 /**
597 * Find any old, orphaned files that should have been deleted.
598 *
599 * These files can get left behind, eg, if you use the Joomla
600 * upgrade procedure.
601 *
602 * The earlier we can do this, the better - don't want upgrade logic
603 * to inadvertently rely on old/relocated files.
604 *
605 * @param \CRM_Queue_TaskContext $ctx
606 * @param string $postUpgradeMessageFile
607 * @return bool
608 */
609 public static function doFileCleanup(CRM_Queue_TaskContext $ctx, $postUpgradeMessageFile) {
610 $source = new CRM_Utils_Check_Component_Source();
611 $files = $source->findOrphanedFiles();
be2fb01f 612 $errors = [];
9e799b1d
TO
613 foreach ($files as $file) {
614 if (is_dir($file['path'])) {
615 @rmdir($file['path']);
616 }
617 else {
618 @unlink($file['path']);
619 }
620
621 if (file_exists($file['path'])) {
622 $errors[] = sprintf("<li>%s</li>", htmlentities($file['path']));
623 }
624 }
625
626 if (!empty($errors)) {
627 file_put_contents($postUpgradeMessageFile,
628 '<br/><br/>' . ts('Some old files could not be removed. Please remove them.')
629 . '<ul>' . implode("\n", $errors) . '</ul>',
630 FILE_APPEND
631 );
632 }
633
634 return TRUE;
635 }
636
e4c4f267 637 /**
df7a1988 638 * Disable/uninstall any extensions not compatible with this new version.
e4c4f267
CW
639 *
640 * @param \CRM_Queue_TaskContext $ctx
641 * @param string $postUpgradeMessageFile
642 * @return bool
643 */
644 public static function disableOldExtensions(CRM_Queue_TaskContext $ctx, $postUpgradeMessageFile) {
df7a1988 645 $messages = [];
e4c4f267 646 $manager = CRM_Extension_System::singleton()->getManager();
df7a1988 647 foreach ($manager->getStatuses() as $key => $status) {
d5abe16a 648 $enableReplacement = CRM_Core_DAO::singleValueQuery('SELECT is_active FROM civicrm_extension WHERE full_name = %1', [1 => [$key, 'String']]);
df7a1988
CW
649 $obsolete = $manager->isIncompatible($key);
650 if ($obsolete) {
651 if (!empty($obsolete['disable']) && in_array($status, [$manager::STATUS_INSTALLED, $manager::STATUS_INSTALLED_MISSING])) {
652 try {
653 $manager->disable($key);
654 // Update the status for the sake of uninstall below.
655 $status = $status == $manager::STATUS_INSTALLED ? $manager::STATUS_DISABLED : $manager::STATUS_DISABLED_MISSING;
656 // This message is intentionally overwritten by uninstall below as it would be redundant
657 $messages[$key] = ts('The extension %1 is now obsolete and has been disabled.', [1 => $key]);
658 }
659 catch (CRM_Extension_Exception $e) {
660 $messages[] = ts('The obsolete extension %1 could not be removed due to an error. It is recommended to remove this extension manually.', [1 => $key]);
661 }
662 }
663 if (!empty($obsolete['uninstall']) && in_array($status, [$manager::STATUS_DISABLED, $manager::STATUS_DISABLED_MISSING])) {
664 try {
665 $manager->uninstall($key);
666 $messages[$key] = ts('The extension %1 is now obsolete and has been uninstalled.', [1 => $key]);
667 if ($status == $manager::STATUS_DISABLED) {
668 $messages[$key] .= ' ' . ts('You can remove it from your extensions directory.');
669 }
670 }
671 catch (CRM_Extension_Exception $e) {
672 $messages[] = ts('The obsolete extension %1 could not be removed due to an error. It is recommended to remove this extension manually.', [1 => $key]);
673 }
674 }
8a0199a3
TO
675 if (!empty($obsolete['force-uninstall'])) {
676 CRM_Core_DAO::executeQuery('UPDATE civicrm_extension SET is_active = 0 WHERE full_name = %1', [
677 1 => [$key, 'String'],
678 ]);
679 }
d5abe16a
SL
680 if (!empty($obsolete['replacement']) && $enableReplacement) {
681 try {
682 $manager->enable($manager->install($obsolete['replacement']));
683 $messages[] = ts('A replacement extension %1 has been installed as you had the obsolete extension %2 installed', [1 => $obsolete['replacement'], 2 => $key]);
684 }
685 catch (CRM_Extension_Exception $e) {
686 $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']]);
687 }
688 }
e4c4f267
CW
689 }
690 }
df7a1988 691 if ($messages) {
e4c4f267 692 file_put_contents($postUpgradeMessageFile,
df7a1988 693 '<br/><br/><ul><li>' . implode("</li>\n<li>", $messages) . '</li></ul>',
e4c4f267
CW
694 FILE_APPEND
695 );
696 }
697
698 return TRUE;
699 }
700
6a488035 701 /**
fe482240 702 * Perform an incremental version update.
6a488035 703 *
77b97be7 704 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
705 * @param string $rev
706 * the target (intermediate) revision e.g '3.2.alpha1'.
77b97be7
EM
707 *
708 * @return bool
6a488035 709 */
00be9182 710 public static function doIncrementalUpgradeStart(CRM_Queue_TaskContext $ctx, $rev) {
6a488035
TO
711 $upgrade = new CRM_Upgrade_Form();
712
713 // as soon as we start doing anything we append ".upgrade" to version.
714 // this also helps detect any partial upgrade issues
715 $upgrade->setVersion($rev . '.upgrade');
716
717 return TRUE;
718 }
719
720 /**
fe482240 721 * Perform an incremental version update.
6a488035 722 *
77b97be7 723 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
724 * @param string $rev
725 * the target (intermediate) revision e.g '3.2.alpha1'.
726 * @param string $originalVer
727 * the original revision.
728 * @param string $latestVer
729 * the target (final) revision.
730 * @param string $postUpgradeMessageFile
731 * path of a modifiable file which lists the post-upgrade messages.
77b97be7
EM
732 *
733 * @return bool
6a488035 734 */
e418776c 735 public static function doIncrementalUpgradeStep(CRM_Queue_TaskContext $ctx, $rev, $originalVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
736 $upgrade = new CRM_Upgrade_Form();
737
738 $phpFunctionName = 'upgrade_' . str_replace('.', '_', $rev);
739
bd00780f
CW
740 $versionObject = $upgrade->incrementalPhpObject($rev);
741
bd00780f 742 $upgrade->setSchemaStructureTables($rev);
6a488035 743
be2fb01f 744 if (is_callable([$versionObject, $phpFunctionName])) {
bd00780f
CW
745 $versionObject->$phpFunctionName($rev, $originalVer, $latestVer);
746 }
747 else {
b871c758 748 $ctx->log->info("Upgrade DB to $rev: SQL");
bd00780f
CW
749 $upgrade->processSQL($rev);
750 }
751
752 // set post-upgrade-message if any
be2fb01f 753 if (is_callable([$versionObject, 'setPostUpgradeMessage'])) {
bd00780f
CW
754 $postUpgradeMessage = file_get_contents($postUpgradeMessageFile);
755 $versionObject->setPostUpgradeMessage($postUpgradeMessage, $rev);
bd00780f 756 file_put_contents($postUpgradeMessageFile, $postUpgradeMessage);
6a488035
TO
757 }
758
759 return TRUE;
760 }
761
762 /**
bd6c6256
TO
763 * Mark an incremental update as finished.
764 *
765 * This method may be called in two cases:
766 *
767 * - After performing each incremental update (`X.X.X.mysql.tpl` or `upgrade_X_X_X()`)
768 * - If needed, one more time at the end of the upgrade for the final version-number.
6a488035 769 *
77b97be7 770 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
771 * @param string $rev
772 * the target (intermediate) revision e.g '3.2.alpha1'.
773 * @param string $currentVer
774 * the original revision.
775 * @param string $latestVer
776 * the target (final) revision.
777 * @param string $postUpgradeMessageFile
778 * path of a modifiable file which lists the post-upgrade messages.
77b97be7
EM
779 *
780 * @return bool
6a488035 781 */
00be9182 782 public static function doIncrementalUpgradeFinish(CRM_Queue_TaskContext $ctx, $rev, $currentVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
783 $upgrade = new CRM_Upgrade_Form();
784 $upgrade->setVersion($rev);
785 CRM_Utils_System::flushCache();
ac05cde3 786
6a488035
TO
787 return TRUE;
788 }
789
c09c9d3d
TO
790 /**
791 * Finalize the core upgrade.
792 *
793 * @return bool
794 * @throws \CRM_Core_Exception
795 */
796 public static function doCoreFinish(): bool {
ecb0ae5d
TO
797 Civi::dispatcher()->setDispatchPolicy(\CRM_Upgrade_DispatchPolicy::get('upgrade.finish'));
798 $restore = \CRM_Utils_AutoClean::with(function() {
799 Civi::dispatcher()->setDispatchPolicy(\CRM_Upgrade_DispatchPolicy::get('upgrade.main'));
800 });
801
6a488035
TO
802 $upgrade = new CRM_Upgrade_Form();
803 list($ignore, $latestVer) = $upgrade->getUpgradeVersions();
804 // Seems extraneous in context, but we'll preserve old behavior
805 $upgrade->setVersion($latestVer);
806
f3209f6d
TO
807 $config = CRM_Core_Config::singleton();
808 $config->userSystem->flush();
809
b00d6b3f 810 CRM_Core_Invoke::rebuildMenuAndCaches(FALSE, FALSE);
3af5c3fe 811 // NOTE: triggerRebuild is FALSE becaues it will run again in a moment (via fixSchemaDifferences).
b00d6b3f 812 // sessionReset is FALSE because upgrade status/postUpgradeMessages are needed by the Page. We reset later in doFinish().
6a488035 813
6b4bec74
CW
814 $versionCheck = new CRM_Utils_VersionCheck();
815 $versionCheck->flushCache();
816
6a488035
TO
817 // Rebuild all triggers and re-enable logging if needed
818 $logging = new CRM_Logging_Schema();
819 $logging->fixSchemaDifferences();
756d9e0d
SL
820 // Force a rebuild of CiviCRM asset cache in case things have changed.
821 \Civi::service('asset_builder')->clear(FALSE);
c09c9d3d
TO
822
823 return TRUE;
824 }
825
826 /**
827 * After finishing the queue, the upgrade-runner calls `doFinish()`.
828 *
829 * This is called by all upgrade-runners (inside or outside of `civicrm-core.git`).
830 * Removing it would be a breaky-annoying process; it would foreclose future use;
831 * and it would produce no tangible benefits.
832 *
833 * @return bool
834 */
835 public static function doFinish(): bool {
efea3f50
TO
836 $session = CRM_Core_Session::singleton();
837 $session->reset(2);
c09c9d3d 838 return TRUE;
6a488035
TO
839 }
840
841 /**
842 * Compute any messages which should be displayed before upgrade
843 * by calling the 'setPreUpgradeMessage' on each incremental upgrade
844 * object.
845 *
5a4f6742
CW
846 * @param string $preUpgradeMessage
847 * alterable.
77b97be7
EM
848 * @param $currentVer
849 * @param $latestVer
6a488035 850 */
00be9182 851 public function setPreUpgradeMessage(&$preUpgradeMessage, $currentVer, $latestVer) {
49368097
CW
852 // check for changed message templates
853 CRM_Upgrade_Incremental_General::checkMessageTemplate($preUpgradeMessage, $latestVer, $currentVer);
854 // set global messages
855 CRM_Upgrade_Incremental_General::setPreUpgradeMessage($preUpgradeMessage, $currentVer, $latestVer);
6a488035
TO
856
857 // Scan through all php files and see if any file is interested in setting pre-upgrade-message
858 // based on $currentVer, $latestVer.
859 // Please note, at this point upgrade hasn't started executing queries.
860 $revisions = $this->getRevisionSequence();
861 foreach ($revisions as $rev) {
49368097 862 if (version_compare($currentVer, $rev) < 0) {
6a488035 863 $versionObject = $this->incrementalPhpObject($rev);
fe83c251 864 CRM_Upgrade_Incremental_General::updateMessageTemplate($preUpgradeMessage, $rev);
be2fb01f 865 if (is_callable([$versionObject, 'setPreUpgradeMessage'])) {
e418776c
TO
866 $versionObject->setPreUpgradeMessage($preUpgradeMessage, $rev, $currentVer);
867 }
6a488035
TO
868 }
869 }
870 }
96025800 871
6a488035 872}