Update copyright date for 2020
[civicrm-core.git] / CRM / Upgrade / Form.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
f299f7db 6 | Copyright CiviCRM LLC (c) 2004-2020 |
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
f299f7db 31 * @copyright CiviCRM LLC (c) 2004-2020
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 }
8a0199a3
TO
676 if (!empty($obsolete['force-uninstall'])) {
677 CRM_Core_DAO::executeQuery('UPDATE civicrm_extension SET is_active = 0 WHERE full_name = %1', [
678 1 => [$key, 'String'],
679 ]);
680 }
e4c4f267
CW
681 }
682 }
df7a1988 683 if ($messages) {
e4c4f267 684 file_put_contents($postUpgradeMessageFile,
df7a1988 685 '<br/><br/><ul><li>' . implode("</li>\n<li>", $messages) . '</li></ul>',
e4c4f267
CW
686 FILE_APPEND
687 );
688 }
689
690 return TRUE;
691 }
692
6a488035 693 /**
fe482240 694 * Perform an incremental version update.
6a488035 695 *
77b97be7 696 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
697 * @param string $rev
698 * the target (intermediate) revision e.g '3.2.alpha1'.
77b97be7
EM
699 *
700 * @return bool
6a488035 701 */
00be9182 702 public static function doIncrementalUpgradeStart(CRM_Queue_TaskContext $ctx, $rev) {
6a488035
TO
703 $upgrade = new CRM_Upgrade_Form();
704
705 // as soon as we start doing anything we append ".upgrade" to version.
706 // this also helps detect any partial upgrade issues
707 $upgrade->setVersion($rev . '.upgrade');
708
709 return TRUE;
710 }
711
712 /**
fe482240 713 * Perform an incremental version update.
6a488035 714 *
77b97be7 715 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
716 * @param string $rev
717 * the target (intermediate) revision e.g '3.2.alpha1'.
718 * @param string $originalVer
719 * the original revision.
720 * @param string $latestVer
721 * the target (final) revision.
722 * @param string $postUpgradeMessageFile
723 * path of a modifiable file which lists the post-upgrade messages.
77b97be7
EM
724 *
725 * @return bool
6a488035 726 */
e418776c 727 public static function doIncrementalUpgradeStep(CRM_Queue_TaskContext $ctx, $rev, $originalVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
728 $upgrade = new CRM_Upgrade_Form();
729
730 $phpFunctionName = 'upgrade_' . str_replace('.', '_', $rev);
731
bd00780f
CW
732 $versionObject = $upgrade->incrementalPhpObject($rev);
733
734 // pre-db check for major release.
735 if ($upgrade->checkVersionRelease($rev, 'alpha1')) {
be2fb01f 736 if (!(is_callable([$versionObject, 'verifyPreDBstate']))) {
bd00780f 737 CRM_Core_Error::fatal("verifyPreDBstate method was not found for $rev");
6a488035 738 }
6a488035 739
bd00780f
CW
740 $error = NULL;
741 if (!($versionObject->verifyPreDBstate($error))) {
742 if (!isset($error)) {
743 $error = "post-condition failed for current upgrade for $rev";
6a488035 744 }
bd00780f 745 CRM_Core_Error::fatal($error);
6a488035
TO
746 }
747
bd00780f 748 }
6a488035 749
bd00780f 750 $upgrade->setSchemaStructureTables($rev);
6a488035 751
be2fb01f 752 if (is_callable([$versionObject, $phpFunctionName])) {
bd00780f
CW
753 $versionObject->$phpFunctionName($rev, $originalVer, $latestVer);
754 }
755 else {
756 $upgrade->processSQL($rev);
757 }
758
759 // set post-upgrade-message if any
be2fb01f 760 if (is_callable([$versionObject, 'setPostUpgradeMessage'])) {
bd00780f
CW
761 $postUpgradeMessage = file_get_contents($postUpgradeMessageFile);
762 $versionObject->setPostUpgradeMessage($postUpgradeMessage, $rev);
bd00780f 763 file_put_contents($postUpgradeMessageFile, $postUpgradeMessage);
6a488035
TO
764 }
765
766 return TRUE;
767 }
768
769 /**
fe482240 770 * Perform an incremental version update.
6a488035 771 *
77b97be7 772 * @param CRM_Queue_TaskContext $ctx
5a4f6742
CW
773 * @param string $rev
774 * the target (intermediate) revision e.g '3.2.alpha1'.
775 * @param string $currentVer
776 * the original revision.
777 * @param string $latestVer
778 * the target (final) revision.
779 * @param string $postUpgradeMessageFile
780 * path of a modifiable file which lists the post-upgrade messages.
77b97be7
EM
781 *
782 * @return bool
6a488035 783 */
00be9182 784 public static function doIncrementalUpgradeFinish(CRM_Queue_TaskContext $ctx, $rev, $currentVer, $latestVer, $postUpgradeMessageFile) {
6a488035
TO
785 $upgrade = new CRM_Upgrade_Form();
786 $upgrade->setVersion($rev);
787 CRM_Utils_System::flushCache();
ac05cde3 788
d8a4acc0
C
789 $config = CRM_Core_Config::singleton();
790 $config->userSystem->flush();
6a488035
TO
791 return TRUE;
792 }
793
00be9182 794 public static function doFinish() {
6a488035
TO
795 $upgrade = new CRM_Upgrade_Form();
796 list($ignore, $latestVer) = $upgrade->getUpgradeVersions();
797 // Seems extraneous in context, but we'll preserve old behavior
798 $upgrade->setVersion($latestVer);
799
f806379b
TO
800 // Clear cached metadata.
801 Civi::service('settings_manager')->flush();
b6386d8c 802
6a488035
TO
803 // cleanup caches CRM-8739
804 $config = CRM_Core_Config::singleton();
1fcf16cc 805 $config->cleanupCaches(1);
6a488035 806
6b4bec74
CW
807 $versionCheck = new CRM_Utils_VersionCheck();
808 $versionCheck->flushCache();
809
6a488035
TO
810 // Rebuild all triggers and re-enable logging if needed
811 $logging = new CRM_Logging_Schema();
812 $logging->fixSchemaDifferences();
813 }
814
815 /**
816 * Compute any messages which should be displayed before upgrade
817 * by calling the 'setPreUpgradeMessage' on each incremental upgrade
818 * object.
819 *
5a4f6742
CW
820 * @param string $preUpgradeMessage
821 * alterable.
77b97be7
EM
822 * @param $currentVer
823 * @param $latestVer
6a488035 824 */
00be9182 825 public function setPreUpgradeMessage(&$preUpgradeMessage, $currentVer, $latestVer) {
49368097
CW
826 // check for changed message templates
827 CRM_Upgrade_Incremental_General::checkMessageTemplate($preUpgradeMessage, $latestVer, $currentVer);
828 // set global messages
829 CRM_Upgrade_Incremental_General::setPreUpgradeMessage($preUpgradeMessage, $currentVer, $latestVer);
6a488035
TO
830
831 // Scan through all php files and see if any file is interested in setting pre-upgrade-message
832 // based on $currentVer, $latestVer.
833 // Please note, at this point upgrade hasn't started executing queries.
834 $revisions = $this->getRevisionSequence();
835 foreach ($revisions as $rev) {
49368097 836 if (version_compare($currentVer, $rev) < 0) {
6a488035 837 $versionObject = $this->incrementalPhpObject($rev);
fe83c251 838 CRM_Upgrade_Incremental_General::updateMessageTemplate($preUpgradeMessage, $rev);
be2fb01f 839 if (is_callable([$versionObject, 'setPreUpgradeMessage'])) {
e418776c
TO
840 $versionObject->setPreUpgradeMessage($preUpgradeMessage, $rev, $currentVer);
841 }
6a488035
TO
842 }
843 }
844 }
96025800 845
6a488035 846}