Commit | Line | Data |
---|---|---|
bf6a5362 CW |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
bf6a5362 | 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 | | |
bf6a5362 CW |
9 | +--------------------------------------------------------------------+ |
10 | */ | |
11 | ||
ff6f993e | 12 | use Civi\Core\SettingsBag; |
13 | ||
bf6a5362 CW |
14 | /** |
15 | * Base class for incremental upgrades | |
16 | */ | |
17 | class CRM_Upgrade_Incremental_Base { | |
18 | const BATCH_SIZE = 5000; | |
19 | ||
d7e4d371 TO |
20 | /** |
21 | * @var string|null | |
22 | */ | |
23 | protected $majorMinor; | |
24 | ||
25 | /** | |
26 | * Get the major and minor version for this class (based on English-style class name). | |
27 | * | |
28 | * @return string | |
29 | * Ex: '5.34' or '4.7' | |
30 | */ | |
31 | public function getMajorMinor() { | |
32 | if (!$this->majorMinor) { | |
33 | $className = explode('_', static::CLASS); | |
34 | $numbers = preg_split("/([[:upper:]][[:lower:]]+)/", array_pop($className), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); | |
35 | $major = CRM_Utils_EnglishNumber::toInt(array_shift($numbers)); | |
36 | $minor = CRM_Utils_EnglishNumber::toInt(implode('', $numbers)); | |
37 | $this->majorMinor = $major . '.' . $minor; | |
38 | } | |
39 | return $this->majorMinor; | |
40 | } | |
41 | ||
42 | /** | |
43 | * Get a list of revisions (PATCH releases) related to this class. | |
44 | * | |
45 | * @return array | |
46 | * Ex: ['4.5.6', '4.5.7'] | |
47 | * @throws \ReflectionException | |
48 | */ | |
49 | public function getRevisionSequence() { | |
50 | $revList = []; | |
51 | ||
52 | $sqlGlob = implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'sql', $this->getMajorMinor() . '.*.mysql.tpl']); | |
53 | $sqlFiles = glob($sqlGlob);; | |
54 | foreach ($sqlFiles as $file) { | |
55 | $revList[] = str_replace('.mysql.tpl', '', basename($file)); | |
56 | } | |
57 | ||
58 | $c = new ReflectionClass(static::class); | |
59 | foreach ($c->getMethods() as $method) { | |
60 | /** @var \ReflectionMethod $method */ | |
61 | if (preg_match(';^upgrade_([0-9_alphabeta]+)$;', $method->getName(), $m)) { | |
62 | $revList[] = str_replace('_', '.', $m[1]); | |
63 | } | |
64 | } | |
65 | ||
66 | $revList = array_unique($revList); | |
67 | usort($revList, 'version_compare'); | |
68 | return $revList; | |
69 | } | |
70 | ||
bf6a5362 CW |
71 | /** |
72 | * Compute any messages which should be displayed before upgrade. | |
73 | * | |
76a2bc7c | 74 | * Downstream classes should implement this method to generate their messages. |
bf6a5362 | 75 | * |
76a2bc7c TO |
76 | * This method will be invoked multiple times. Implementations MUST consult the `$rev` |
77 | * before deciding what messages to add. See the examples linked below. | |
78 | * | |
79 | * @see \CRM_Upgrade_Incremental_php_FourSeven::setPreUpgradeMessage() | |
80 | * @see \CRM_Upgrade_Incremental_php_FiveTwenty::setPreUpgradeMessage() | |
81 | * | |
82 | * @param string $preUpgradeMessage | |
83 | * Accumulated list of messages. Alterable. | |
bf6a5362 | 84 | * @param string $rev |
76a2bc7c TO |
85 | * The incremental version number. (Called repeatedly, once for each increment.) |
86 | * | |
87 | * Ex: Suppose the system upgrades from 5.7.3 to 5.10.0. The method FiveEight::setPreUpgradeMessage() | |
88 | * will be called for each increment of '5.8.*' ('5.8.alpha1' => '5.8.beta1' => '5.8.0'). | |
bf6a5362 | 89 | * @param null $currentVer |
76a2bc7c TO |
90 | * This is the penultimate version targeted by the upgrader. |
91 | * Equivalent to CRM_Utils_System::version(). | |
bf6a5362 CW |
92 | */ |
93 | public function setPreUpgradeMessage(&$preUpgradeMessage, $rev, $currentVer = NULL) { | |
94 | } | |
95 | ||
96 | /** | |
97 | * Compute any messages which should be displayed after upgrade. | |
98 | * | |
76a2bc7c TO |
99 | * Downstream classes should implement this method to generate their messages. |
100 | * | |
101 | * This method will be invoked multiple times. Implementations MUST consult the `$rev` | |
102 | * before deciding what messages to add. See the examples linked below. | |
103 | * | |
104 | * @see \CRM_Upgrade_Incremental_php_FourSeven::setPostUpgradeMessage() | |
105 | * @see \CRM_Upgrade_Incremental_php_FiveTwentyOne::setPostUpgradeMessage() | |
106 | * | |
bf6a5362 | 107 | * @param string $postUpgradeMessage |
76a2bc7c | 108 | * Accumulated list of messages. Alterable. |
bf6a5362 | 109 | * @param string $rev |
76a2bc7c TO |
110 | * The incremental version number. (Called repeatedly, once for each increment.) |
111 | * | |
112 | * Ex: Suppose the system upgrades from 5.7.3 to 5.10.0. The method FiveEight::setPreUpgradeMessage() | |
113 | * will be called for each increment of '5.8.*' ('5.8.alpha1' => '5.8.beta1' => '5.8.0'). | |
bf6a5362 CW |
114 | */ |
115 | public function setPostUpgradeMessage(&$postUpgradeMessage, $rev) { | |
116 | } | |
117 | ||
bf6a5362 CW |
118 | /** |
119 | * (Queue Task Callback) | |
54957108 | 120 | * |
121 | * @param \CRM_Queue_TaskContext $ctx | |
122 | * @param string $rev | |
123 | * | |
124 | * @return bool | |
bf6a5362 CW |
125 | */ |
126 | public static function runSql(CRM_Queue_TaskContext $ctx, $rev) { | |
127 | $upgrade = new CRM_Upgrade_Form(); | |
128 | $upgrade->processSQL($rev); | |
129 | ||
130 | return TRUE; | |
131 | } | |
132 | ||
133 | /** | |
54957108 | 134 | * Syntactic sugar for adding a task. |
135 | * | |
136 | * Task is (a) in this class and (b) has a high priority. | |
bf6a5362 CW |
137 | * |
138 | * After passing the $funcName, you can also pass parameters that will go to | |
139 | * the function. Note that all params must be serializable. | |
54957108 | 140 | * |
141 | * @param string $title | |
142 | * @param string $funcName | |
bf6a5362 CW |
143 | */ |
144 | protected function addTask($title, $funcName) { | |
be2fb01f | 145 | $queue = CRM_Queue_Service::singleton()->load([ |
bf6a5362 CW |
146 | 'type' => 'Sql', |
147 | 'name' => CRM_Upgrade_Form::QUEUE_NAME, | |
be2fb01f | 148 | ]); |
bf6a5362 CW |
149 | |
150 | $args = func_get_args(); | |
151 | $title = array_shift($args); | |
152 | $funcName = array_shift($args); | |
153 | $task = new CRM_Queue_Task( | |
be2fb01f | 154 | [get_class($this), $funcName], |
bf6a5362 CW |
155 | $args, |
156 | $title | |
157 | ); | |
be2fb01f | 158 | $queue->createItem($task, ['weight' => -1]); |
bf6a5362 CW |
159 | } |
160 | ||
058b8a5e CW |
161 | /** |
162 | * Remove a payment processor if not in use | |
163 | * | |
87a33a95 CW |
164 | * @param CRM_Queue_TaskContext $ctx |
165 | * @param string $name | |
166 | * @return bool | |
058b8a5e CW |
167 | * @throws \CiviCRM_API3_Exception |
168 | */ | |
87a33a95 | 169 | public static function removePaymentProcessorType(CRM_Queue_TaskContext $ctx, $name) { |
be2fb01f | 170 | $processors = civicrm_api3('PaymentProcessor', 'getcount', ['payment_processor_type_id' => $name]); |
058b8a5e | 171 | if (empty($processors['result'])) { |
be2fb01f | 172 | $result = civicrm_api3('PaymentProcessorType', 'get', [ |
058b8a5e CW |
173 | 'name' => $name, |
174 | 'return' => 'id', | |
be2fb01f | 175 | ]); |
058b8a5e | 176 | if (!empty($result['id'])) { |
be2fb01f | 177 | civicrm_api3('PaymentProcessorType', 'delete', ['id' => $result['id']]); |
058b8a5e CW |
178 | } |
179 | } | |
87a33a95 | 180 | return TRUE; |
058b8a5e CW |
181 | } |
182 | ||
27e82c24 SL |
183 | /** |
184 | * @param string $table_name | |
185 | * @param string $constraint_name | |
186 | * @return bool | |
187 | */ | |
188 | public static function checkFKExists($table_name, $constraint_name) { | |
189 | return CRM_Core_BAO_SchemaHandler::checkFKExists($table_name, $constraint_name); | |
190 | } | |
191 | ||
b412f764 CW |
192 | /** |
193 | * Add a column to a table if it doesn't already exist | |
194 | * | |
195 | * @param CRM_Queue_TaskContext $ctx | |
196 | * @param string $table | |
197 | * @param string $column | |
198 | * @param string $properties | |
61612722 | 199 | * @param bool $localizable is this a field that should be localized |
9f266042 | 200 | * @param string|null $version CiviCRM version to use if rebuilding multilingual schema |
35fd9d21 | 201 | * @param bool $triggerRebuild should we trigger the rebuild of the multilingual schema |
9f266042 | 202 | * |
b412f764 CW |
203 | * @return bool |
204 | */ | |
35fd9d21 | 205 | public static function addColumn($ctx, $table, $column, $properties, $localizable = FALSE, $version = NULL, $triggerRebuild = TRUE) { |
394d18d3 | 206 | $locales = CRM_Core_I18n::getMultilingual(); |
be2fb01f | 207 | $queries = []; |
ce33da5a | 208 | if (!CRM_Core_BAO_SchemaHandler::checkIfFieldExists($table, $column, FALSE)) { |
394d18d3 | 209 | if ($locales) { |
61612722 | 210 | if ($localizable) { |
61612722 | 211 | foreach ($locales as $locale) { |
ce33da5a | 212 | if (!CRM_Core_BAO_SchemaHandler::checkIfFieldExists($table, "{$column}_{$locale}", FALSE)) { |
41ace555 SL |
213 | $queries[] = "ALTER TABLE `$table` ADD COLUMN `{$column}_{$locale}` $properties"; |
214 | } | |
61612722 SL |
215 | } |
216 | } | |
217 | else { | |
218 | $queries[] = "ALTER TABLE `$table` ADD COLUMN `$column` $properties"; | |
219 | } | |
220 | } | |
221 | else { | |
222 | $queries[] = "ALTER TABLE `$table` ADD COLUMN `$column` $properties"; | |
223 | } | |
224 | foreach ($queries as $query) { | |
be2fb01f | 225 | CRM_Core_DAO::executeQuery($query, [], TRUE, NULL, FALSE, FALSE); |
61612722 SL |
226 | } |
227 | } | |
35fd9d21 | 228 | if ($locales && $triggerRebuild) { |
76ee148d | 229 | CRM_Core_I18n_Schema::rebuildMultilingualSchema($locales, $version, TRUE); |
150676ad | 230 | } |
b412f764 CW |
231 | return TRUE; |
232 | } | |
233 | ||
c87e11eb EM |
234 | /** |
235 | * Add the specified option group, gracefully if it already exists. | |
236 | * | |
237 | * @param CRM_Queue_TaskContext $ctx | |
238 | * @param array $params | |
239 | * @param array $options | |
240 | * | |
241 | * @return bool | |
242 | */ | |
243 | public static function addOptionGroup(CRM_Queue_TaskContext $ctx, $params, $options): bool { | |
244 | $defaults = ['is_active' => 1]; | |
245 | $optionDefaults = ['is_active' => 1]; | |
246 | $optionDefaults['option_group_id'] = \CRM_Core_BAO_OptionGroup::ensureOptionGroupExists(array_merge($defaults, $params)); | |
247 | ||
248 | foreach ($options as $option) { | |
249 | \CRM_Core_BAO_OptionValue::ensureOptionValueExists(array_merge($optionDefaults, $option)); | |
250 | } | |
251 | return TRUE; | |
252 | } | |
253 | ||
fe83c251 | 254 | /** |
255 | * Do any relevant message template updates. | |
256 | * | |
257 | * @param CRM_Queue_TaskContext $ctx | |
258 | * @param string $version | |
259 | */ | |
260 | public static function updateMessageTemplates($ctx, $version) { | |
261 | $messageTemplateObject = new CRM_Upgrade_Incremental_MessageTemplates($version); | |
262 | $messageTemplateObject->updateTemplates(); | |
8515e10c | 263 | } |
fe83c251 | 264 | |
8515e10c EM |
265 | /** |
266 | * Updated a message token within a template. | |
267 | * | |
268 | * @param CRM_Queue_TaskContext $ctx | |
269 | * @param string $workflowName | |
270 | * @param string $old | |
271 | * @param string $new | |
272 | * @param $version | |
273 | * | |
274 | * @return bool | |
275 | */ | |
276 | public static function updateMessageToken($ctx, string $workflowName, string $old, string $new, $version):bool { | |
277 | $messageObj = new CRM_Upgrade_Incremental_MessageTemplates($version); | |
fa547918 EM |
278 | if (!empty($workflowName)) { |
279 | $messageObj->replaceTokenInTemplate($workflowName, $old, $new); | |
280 | } | |
281 | else { | |
282 | $messageObj->replaceTokenInMessageTemplates($old, $new); | |
283 | } | |
8515e10c | 284 | return TRUE; |
fe83c251 | 285 | } |
286 | ||
b2148ce1 | 287 | /** |
8ff591cb | 288 | * Updated a message token within a scheduled reminder. |
b2148ce1 EM |
289 | * |
290 | * @param CRM_Queue_TaskContext $ctx | |
291 | * @param string $old | |
292 | * @param string $new | |
293 | * @param $version | |
294 | * | |
295 | * @return bool | |
296 | */ | |
297 | public static function updateActionScheduleToken($ctx, string $old, string $new, $version):bool { | |
298 | $messageObj = new CRM_Upgrade_Incremental_MessageTemplates($version); | |
299 | $messageObj->replaceTokenInActionSchedule($old, $new); | |
300 | return TRUE; | |
301 | } | |
302 | ||
8ff591cb | 303 | /** |
2f7db718 | 304 | * Updated a message token within a label. |
8ff591cb EM |
305 | * |
306 | * @param CRM_Queue_TaskContext $ctx | |
307 | * @param string $old | |
308 | * @param string $new | |
309 | * @param $version | |
310 | * | |
311 | * @return bool | |
312 | */ | |
313 | public static function updatePrintLabelToken($ctx, string $old, string $new, $version):bool { | |
314 | $messageObj = new CRM_Upgrade_Incremental_MessageTemplates($version); | |
315 | $messageObj->replaceTokenInPrintLabel($old, $new); | |
316 | return TRUE; | |
317 | } | |
318 | ||
2f7db718 EM |
319 | /** |
320 | * Updated a message token within greeting options. | |
321 | * | |
322 | * @param CRM_Queue_TaskContext $ctx | |
323 | * @param string $old | |
324 | * @param string $new | |
325 | * @param $version | |
326 | * | |
327 | * @return bool | |
328 | */ | |
329 | public static function updateGreetingOptions($ctx, string $old, string $new, $version):bool { | |
330 | $messageObj = new CRM_Upgrade_Incremental_MessageTemplates($version); | |
331 | $messageObj->replaceTokenInGreetingOptions($old, $new); | |
332 | return TRUE; | |
333 | } | |
334 | ||
28da6cc0 ML |
335 | /** |
336 | * Updated a currency in civicrm_currency and related configurations | |
337 | * | |
338 | * @param CRM_Queue_TaskContext $ctx | |
339 | * @param string $old_name | |
340 | * @param string $new_name | |
341 | * | |
342 | * @return bool | |
343 | */ | |
344 | public static function updateCurrencyName($ctx, string $old_name, string $new_name): bool { | |
345 | CRM_Core_DAO::executeQuery('UPDATE civicrm_currency SET name = %1 WHERE name = %2', [ | |
346 | 1 => [$new_name, 'String'], | |
347 | 2 => [$old_name, 'String'], | |
348 | ]); | |
349 | ||
350 | $oid = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_option_group WHERE name = 'currencies_enabled'"); | |
351 | if ($oid) { | |
352 | CRM_Core_DAO::executeQuery('UPDATE civicrm_option_value SET value = %1 WHERE value = %2 AND option_group_id = %3', [ | |
353 | 1 => [$new_name, 'String'], | |
354 | 2 => [$old_name, 'String'], | |
355 | 3 => [$oid, 'String'], | |
356 | ]); | |
357 | } | |
358 | ||
359 | CRM_Core_DAO::executeQuery('UPDATE civicrm_participant SET fee_currency = %1 WHERE fee_currency = %2', [ | |
360 | 1 => [$new_name, 'String'], | |
361 | 2 => [$old_name, 'String'], | |
362 | ]); | |
363 | ||
364 | $tables = [ | |
365 | 'civicrm_contribution', | |
366 | 'civicrm_contribution_page', | |
367 | 'civicrm_contribution_recur', | |
368 | 'civicrm_contribution_soft', | |
369 | 'civicrm_event', | |
370 | 'civicrm_financial_item', | |
371 | 'civicrm_financial_trxn', | |
372 | 'civicrm_grant', | |
373 | 'civicrm_pcp', | |
374 | 'civicrm_pledge_payment', | |
375 | 'civicrm_pledge', | |
376 | 'civicrm_product', | |
377 | ]; | |
378 | ||
379 | foreach ($tables as $table) { | |
380 | CRM_Core_DAO::executeQuery('UPDATE %3 SET currency = %1 WHERE currency = %2', [ | |
381 | 1 => [$new_name, 'String'], | |
382 | 2 => [$old_name, 'String'], | |
383 | 3 => [$table, 'MysqlColumnNameOrAlias'], | |
384 | ]); | |
385 | } | |
386 | ||
387 | return TRUE; | |
388 | } | |
389 | ||
504770b4 | 390 | /** |
391 | * Re-save any valid values from contribute settings into the normal setting | |
392 | * format. | |
393 | * | |
394 | * We render the array of contribution_invoice_settings and any that have | |
395 | * metadata defined we add to the correct key. This is safe to run even if no | |
396 | * settings are to be converted, per the test in | |
397 | * testConvertUpgradeContributeSettings. | |
398 | * | |
399 | * @param $ctx | |
400 | * | |
401 | * @return bool | |
402 | */ | |
403 | public static function updateContributeSettings($ctx) { | |
ff6f993e | 404 | // Use a direct query as api now does some handling on this. |
405 | $settings = CRM_Core_DAO::executeQuery("SELECT value, domain_id FROM civicrm_setting WHERE name = 'contribution_invoice_settings'"); | |
406 | ||
407 | while ($settings->fetch()) { | |
408 | $contributionSettings = (array) CRM_Utils_String::unserialize($settings->value); | |
409 | foreach (array_merge(SettingsBag::getContributionInvoiceSettingKeys(), ['deferred_revenue_enabled' => 'deferred_revenue_enabled']) as $possibleKeyName => $settingName) { | |
410 | if (!empty($contributionSettings[$possibleKeyName]) && empty(Civi::settings($settings->domain_id)->getExplicit($settingName))) { | |
411 | Civi::settings($settings->domain_id)->set($settingName, $contributionSettings[$possibleKeyName]); | |
412 | } | |
413 | } | |
504770b4 | 414 | } |
415 | return TRUE; | |
416 | } | |
417 | ||
7015248a | 418 | /** |
419 | * Do any relevant smart group updates. | |
420 | * | |
421 | * @param CRM_Queue_TaskContext $ctx | |
ac241c34 | 422 | * @param array $actions |
7015248a | 423 | * |
424 | * @return bool | |
425 | */ | |
adcd5156 | 426 | public static function updateSmartGroups($ctx, $actions) { |
ac241c34 CW |
427 | $groupUpdateObject = new CRM_Upgrade_Incremental_SmartGroups(); |
428 | $groupUpdateObject->updateGroups($actions); | |
7015248a | 429 | return TRUE; |
430 | } | |
431 | ||
21ca2cb6 | 432 | /** |
433 | * Drop a column from a table if it exist. | |
434 | * | |
435 | * @param CRM_Queue_TaskContext $ctx | |
436 | * @param string $table | |
437 | * @param string $column | |
438 | * @return bool | |
439 | */ | |
440 | public static function dropColumn($ctx, $table, $column) { | |
441 | if (CRM_Core_BAO_SchemaHandler::checkIfFieldExists($table, $column)) { | |
442 | CRM_Core_DAO::executeQuery("ALTER TABLE `$table` DROP COLUMN `$column`", | |
be2fb01f | 443 | [], TRUE, NULL, FALSE, FALSE); |
21ca2cb6 | 444 | } |
445 | return TRUE; | |
446 | } | |
447 | ||
19415e64 | 448 | /** |
449 | * Add a index to a table column. | |
450 | * | |
451 | * @param CRM_Queue_TaskContext $ctx | |
452 | * @param string $table | |
8a493ab9 CW |
453 | * @param string|array $columns |
454 | * @param string $prefix | |
19415e64 | 455 | * @return bool |
456 | */ | |
8a493ab9 CW |
457 | public static function addIndex($ctx, $table, $columns, $prefix = 'index') { |
458 | $tables = [$table => (array) $columns]; | |
459 | CRM_Core_BAO_SchemaHandler::createIndexes($tables, $prefix); | |
19415e64 | 460 | |
461 | return TRUE; | |
462 | } | |
463 | ||
464 | /** | |
465 | * Drop a index from a table if it exist. | |
466 | * | |
467 | * @param CRM_Queue_TaskContext $ctx | |
468 | * @param string $table | |
469 | * @param string $indexName | |
470 | * @return bool | |
471 | */ | |
472 | public static function dropIndex($ctx, $table, $indexName) { | |
473 | CRM_Core_BAO_SchemaHandler::dropIndexIfExists($table, $indexName); | |
474 | ||
475 | return TRUE; | |
476 | } | |
477 | ||
e3ed5029 TO |
478 | /** |
479 | * Drop a table... but only if it's empty. | |
480 | * | |
481 | * @param CRM_Queue_TaskContext $ctx | |
482 | * @param string $table | |
483 | * @return bool | |
484 | */ | |
485 | public static function dropTableIfEmpty($ctx, $table) { | |
486 | if (CRM_Core_DAO::checkTableExists($table)) { | |
487 | if (!CRM_Core_DAO::checkTableHasData($table)) { | |
488 | CRM_Core_BAO_SchemaHandler::dropTable($table); | |
489 | } | |
490 | else { | |
491 | $ctx->log->warning("dropTableIfEmpty($table): Found data. Preserved table."); | |
492 | } | |
493 | } | |
494 | ||
495 | return TRUE; | |
496 | } | |
497 | ||
81e30d0a SL |
498 | /** |
499 | * Rebuild Multilingual Schema. | |
500 | * @param CRM_Queue_TaskContext $ctx | |
9f266042 | 501 | * @param string|null $version CiviCRM version to use if rebuilding multilingual schema |
502 | * | |
81e30d0a SL |
503 | * @return bool |
504 | */ | |
76ee148d | 505 | public static function rebuildMultilingalSchema($ctx, $version = NULL) { |
394d18d3 CW |
506 | $locales = CRM_Core_I18n::getMultilingual(); |
507 | if ($locales) { | |
76ee148d | 508 | CRM_Core_I18n_Schema::rebuildMultilingualSchema($locales, $version); |
81e30d0a SL |
509 | } |
510 | return TRUE; | |
511 | } | |
512 | ||
bf6a5362 | 513 | } |