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