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