remove never-used option value
[civicrm-core.git] / CRM / Upgrade / Incremental / php / FiveTwenty.php
CommitLineData
187fc9e9
C
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
187fc9e9 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 |
187fc9e9
C
9 +--------------------------------------------------------------------+
10 */
11
12/**
13 * Upgrade logic for FiveTwenty */
14class CRM_Upgrade_Incremental_php_FiveTwenty extends CRM_Upgrade_Incremental_Base {
15
400db156 16 /**
51dda21e 17 * @var array
400db156
D
18 * api call result keyed on relationship_type.id
19 */
20 protected static $relationshipTypes;
21
187fc9e9
C
22 /**
23 * Compute any messages which should be displayed beforeupgrade.
24 *
25 * Note: This function is called iteratively for each upcoming
26 * revision to the database.
27 *
28 * @param string $preUpgradeMessage
29 * @param string $rev
30 * a version number, e.g. '4.4.alpha1', '4.4.beta3', '4.4.0'.
31 * @param null $currentVer
32 */
33 public function setPreUpgradeMessage(&$preUpgradeMessage, $rev, $currentVer = NULL) {
81633334
TO
34 if ($rev == '5.20.alpha1') {
35 if (CRM_Core_DAO::checkTableExists('civicrm_persistent') && CRM_Core_DAO::checkTableHasData('civicrm_persistent')) {
36 $preUpgradeMessage .= '<br/>' . ts("WARNING: The table \"<code>civicrm_persistent</code>\" is flagged for removal because all official records show it being unused. However, the upgrader has detected data in this copy of \"<code>civicrm_persistent</code>\". Please <a href='%1' target='_blank'>report</a> anything you can about the usage of this table. In the mean-time, the data will be preserved.", [
37 1 => 'https://civicrm.org/bug-reporting',
38 ]);
39 }
9649c2a4
D
40
41 $config = CRM_Core_Config::singleton();
42 if (in_array('CiviCase', $config->enableComponents)) {
43 // Do dry-run to get warning messages.
44 $messages = self::_changeCaseTypeLabelToName(TRUE);
45 foreach ($messages as $message) {
46 $preUpgradeMessage .= "<p>{$message}</p>\n";
47 }
48 }
81633334 49 }
187fc9e9
C
50 }
51
52 /**
53 * Compute any messages which should be displayed after upgrade.
54 *
55 * @param string $postUpgradeMessage
56 * alterable.
57 * @param string $rev
58 * an intermediate version; note that setPostUpgradeMessage is called repeatedly with different $revs.
59 */
60 public function setPostUpgradeMessage(&$postUpgradeMessage, $rev) {
61 // Example: Generate a post-upgrade message.
62 // if ($rev == '5.12.34') {
63 // $postUpgradeMessage .= '<br /><br />' . ts("By default, CiviCRM now disables the ability to import directly from SQL. To use this feature, you must explicitly grant permission 'import SQL datasource'.");
64 // }
65 }
66
67 /*
68 * Important! All upgrade functions MUST add a 'runSql' task.
69 * Uncomment and use the following template for a new upgrade version
70 * (change the x in the function name):
71 */
72
187fc9e9
C
73 // public static function taskFoo(CRM_Queue_TaskContext $ctx, ...) {
74 // return TRUE;
75 // }
76
6489e3de
SL
77 /**
78 * Upgrade function.
79 *
80 * @param string $rev
81 */
82 public function upgrade_5_20_alpha1($rev) {
83 $this->addTask('Add frontend title column to contribution page table', 'addColumn', 'civicrm_contribution_page',
84 'frontend_title', "varchar(255) DEFAULT NULL COMMENT 'Contribution Page Public title'", TRUE, '5.20.alpha1');
dc396835
AS
85 $this->addTask('Add is_template field to civicrm_contribution', 'addColumn', 'civicrm_contribution', 'is_template',
86 "tinyint(4) DEFAULT '0' COMMENT 'Shows this is a template for recurring contributions.'", FALSE, '5.20.alpha1');
1f34d30a
AS
87 $this->addTask('Add order_reference field to civicrm_financial_trxn', 'addColumn', 'civicrm_financial_trxn', 'order_reference',
88 "varchar(255) COMMENT 'Payment Processor external order reference'", FALSE, '5.20.alpha1');
400db156
D
89 $config = CRM_Core_Config::singleton();
90 if (in_array('CiviCase', $config->enableComponents)) {
91 $this->addTask('Change direction of autoassignees in case type xml', 'changeCaseTypeAutoassignee');
9649c2a4 92 $this->addTask('Change labels back to names in case type xml', 'changeCaseTypeLabelToName');
400db156 93 }
6489e3de 94 $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
f831ac20 95 $this->addTask('Add "Template" contribution status', 'templateStatus');
7d832c0c
SL
96 $this->addTask('Update smart groups to rename filters on case_from and case_to to case_start_date and case_end_date', 'updateSmartGroups', [
97 'renameField' => [
98 ['old' => 'case_from_relative', 'new' => 'case_start_date_relative'],
99 ['old' => 'case_from_start_date_high', 'new' => 'case_start_date_high'],
100 ['old' => 'case_from_start_date_low', 'new' => 'case_start_date_low'],
101 ['old' => 'case_to_relative', 'new' => 'case_end_date_relative'],
102 ['old' => 'case_to_end_date_high', 'new' => 'case_end_date_high'],
103 ['old' => 'case_to_end_date_low', 'new' => 'case_end_date_low'],
0058ef3f
SL
104 ['old' => 'mailing_date_relative', 'new' => 'mailing_job_start_date_relative'],
105 ['old' => 'mailing_date_high', 'new' => 'mailing_job_start_date_high'],
106 ['old' => 'mailing_date_low', 'new' => 'mailing_job_start_date_low'],
41b8dd1d 107 ['old' => 'relation_start_date_low', 'new' => 'relationship_start_date_low'],
108 ['old' => 'relation_start_date_high', 'new' => 'relationship_start_date_high'],
109 ['old' => 'relation_start_date_relative', 'new' => 'relationship_start_date_relative'],
110 ['old' => 'relation_end_date_low', 'new' => 'relationship_end_date_low'],
111 ['old' => 'relation_end_date_high', 'new' => 'relationship_end_date_high'],
112 ['old' => 'relation_end_date_relative', 'new' => 'relationship_end_date_relative'],
c1436807
SL
113 ['old' => 'event_start_date_low', 'new' => 'event_low'],
114 ['old' => 'event_end_date_high', 'new' => 'event_high'],
7d832c0c
SL
115 ],
116 ]);
70e0d21f
SL
117 $this->addTask('Convert Log date searches to their final names either created date or modified date', 'updateSmartGroups', [
118 'renameLogFields' => [],
119 ]);
7d832c0c
SL
120 $this->addTask('Update smart groups where jcalendar fields have been converted to datepicker', 'updateSmartGroups', [
121 'datepickerConversion' => [
bca2ad39 122 'birth_date',
123 'deceased_date',
7d832c0c
SL
124 'case_start_date',
125 'case_end_date',
0058ef3f 126 'mailing_job_start_date',
41b8dd1d 127 'relationship_start_date',
128 'relationship_end_date',
c1436807
SL
129 'event',
130 'relation_active_period_date',
70e0d21f
SL
131 'created_date',
132 'modified_date',
7d832c0c
SL
133 ],
134 ]);
81633334 135 $this->addTask('Clean up unused table "civicrm_persistent"', 'dropTableIfEmpty', 'civicrm_persistent');
7f054690
SL
136 $this->addTask('Convert Custom data based smart groups from jcalendar to datepicker', 'updateSmartGroups', [
137 'convertCustomSmartGroups' => NULL,
138 ]);
f831ac20
EE
139 }
140
141 public static function templateStatus(CRM_Queue_TaskContext $ctx) {
142 CRM_Core_BAO_OptionValue::ensureOptionValueExists([
143 'option_group_id' => 'contribution_status',
144 'name' => 'Template',
145 'label' => ts('Template'),
146 'is_active' => TRUE,
147 'component_id' => 'CiviContribute',
148 ]);
149 return TRUE;
6489e3de
SL
150 }
151
400db156
D
152 /**
153 * Change direction of activity autoassignees in case type xml for
154 * bidirectional relationship types if they point the other way. This is
155 * mostly a visual issue on the case type edit screen and doesn't affect
156 * normal operation, but could lead to confusion and a future mixup.
157 * (dev/core#1046)
158 * ONLY for ones using database storage - don't want to "fork" case types
159 * that aren't currently forked.
160 *
161 * Earlier iterations of this used the api and array manipulation
162 * and then another iteration used SimpleXML manipulation, but both
163 * suffered from weirdnesses in how conversion back and forth worked.
164 *
165 * Here we use SQL and a regex. The thing we're changing is pretty
166 * well-defined and unique:
167 * <default_assignee_relationship>N_b_a</default_assignee_relationship>
168 *
169 * @return bool
170 */
171 public static function changeCaseTypeAutoassignee() {
172 self::$relationshipTypes = civicrm_api3('RelationshipType', 'get', [
173 'options' => ['limit' => 0],
174 ])['values'];
175
176 // Get all case types definitions that are using db storage
177 $dao = CRM_Core_DAO::executeQuery("SELECT id, definition FROM civicrm_case_type WHERE definition IS NOT NULL AND definition <> ''");
178 while ($dao->fetch()) {
179 self::processCaseTypeAutoassignee($dao->id, $dao->definition);
180 }
181 return TRUE;
182 }
183
184 /**
185 * Process a single case type
186 *
187 * @param $caseTypeId int
188 * @param $definition string
189 * xml string
190 */
191 public static function processCaseTypeAutoassignee($caseTypeId, $definition) {
192 $isDirty = FALSE;
193 // find the autoassignees
194 preg_match_all('/<default_assignee_relationship>(.*?)<\/default_assignee_relationship>/', $definition, $matches);
195 // $matches[1][n] has the text inside the xml tag, e.g. 2_a_b
196 foreach ($matches[1] as $index => $match) {
197 if (empty($match)) {
198 continue;
199 }
200 // parse out existing id and direction
201 list($relationshipTypeId, $direction1) = explode('_', $match);
202 // we only care about ones that are b_a
203 if ($direction1 === 'b') {
204 // we only care about bidirectional
205 if (self::isBidirectionalRelationship($relationshipTypeId)) {
206 // flip it to be a_b
207 // $matches[0][n] has the whole match including the xml tag
208 $definition = str_replace($matches[0][$index], "<default_assignee_relationship>{$relationshipTypeId}_a_b</default_assignee_relationship>", $definition);
209 $isDirty = TRUE;
210 }
211 }
212 }
213
214 if ($isDirty) {
215 $sqlParams = [
216 1 => [$definition, 'String'],
217 2 => [$caseTypeId, 'Integer'],
218 ];
219 CRM_Core_DAO::executeQuery("UPDATE civicrm_case_type SET definition = %1 WHERE id = %2", $sqlParams);
220 //echo "UPDATE civicrm_case_type SET definition = '" . CRM_Core_DAO::escapeString($sqlParams[1][0]) . "' WHERE id = {$sqlParams[2][0]}\n";
221 }
222 }
223
224 /**
225 * Check if this is bidirectional, based on label. In the situation where
226 * we're using this we don't care too much about the edge case where name
227 * might not also be bidirectional.
228 *
229 * @param $relationshipTypeId int
230 *
231 * @return bool
232 */
233 private static function isBidirectionalRelationship($relationshipTypeId) {
234 if (isset(self::$relationshipTypes[$relationshipTypeId])) {
235 if (self::$relationshipTypes[$relationshipTypeId]['label_a_b'] === self::$relationshipTypes[$relationshipTypeId]['label_b_a']) {
236 return TRUE;
237 }
238 }
239 return FALSE;
240 }
241
9649c2a4
D
242 /**
243 * Change labels in case type xml definition back to names. (dev/core#1046)
244 * ONLY for ones using database storage - don't want to "fork" case types
245 * that aren't currently forked.
246 *
247 * @return bool
248 */
249 public static function changeCaseTypeLabelToName() {
250 self::_changeCaseTypeLabelToName(FALSE);
251 return TRUE;
252 }
253
254 /**
255 * Change labels in case type xml definition back to names. (dev/core#1046)
256 * ONLY for ones using database storage - don't want to "fork" case types
257 * that aren't currently forked.
258 *
259 * @param $isDryRun bool
260 * If TRUE then don't actually change anything just report warnings.
261 *
262 * @return array List of warning messages.
263 */
264 public static function _changeCaseTypeLabelToName($isDryRun = FALSE) {
265 $messages = [];
266 self::$relationshipTypes = civicrm_api3('RelationshipType', 'get', [
267 'options' => ['limit' => 0],
268 ])['values'];
269
270 // Get all case types definitions that are using db storage
271 $dao = CRM_Core_DAO::executeQuery("SELECT id FROM civicrm_case_type WHERE definition IS NOT NULL AND definition <> ''");
272 while ($dao->fetch()) {
273 // array_merge so that existing numeric keys don't get overwritten
274 $messages = array_merge($messages, self::_processCaseTypeLabelName($isDryRun, $dao->id));
275 }
276 return $messages;
277 }
278
279 /**
280 * Process a single case type for _changeCaseTypeLabelToName()
281 *
282 * @param $isDryRun bool
283 * If TRUE then don't actually change anything just report warnings.
284 * @param $caseTypeId int
285 */
286 private static function _processCaseTypeLabelName($isDryRun, $caseTypeId) {
287 $messages = [];
288 $isDirty = FALSE;
289
290 // Get the case type definition
291 $caseType = civicrm_api3(
292 'CaseType',
293 'get',
294 ['id' => $caseTypeId]
295 )['values'][$caseTypeId];
296
297 foreach ($caseType['definition']['caseRoles'] as $roleSequenceId => $role) {
298 // First double-check that there is a unique match on label so we
299 // don't get it wrong.
300 // There's maybe a fancy way to do this with array_XXX functions but
301 // need to take into account edge cases where bidirectional but name
302 // is different, or where somehow two labels are the same across types,
303 // so do old-fashioned loop.
304
305 $cantConvertMessage = NULL;
306 $foundName = NULL;
307 foreach (self::$relationshipTypes as $relationshipType) {
308 // does it match one of our existing labels
309 if ($relationshipType['label_a_b'] === $role['name'] || $relationshipType['label_b_a'] === $role['name']) {
310 // So either it's ambiguous, in which case exit loop with a message,
311 // or we have the name, so exit loop with that.
312 $cantConvertMessage = self::checkAmbiguous($relationshipType, $caseType['name'], $role['name']);
313 if (empty($cantConvertMessage)) {
314 // not ambiguous, so note the corresponding name for the direction
315 $foundName = ($relationshipType['label_a_b'] === $role['name']) ? $relationshipType['name_a_b'] : $relationshipType['name_b_a'];
316 }
317 break;
318 }
319 }
320
321 if (empty($foundName) && empty($cantConvertMessage)) {
322 // It's possible we went through all relationship types and didn't
323 // find any match, so don't change anything.
324 $cantConvertMessage = ts("Case Type '%1', role '%2' doesn't seem to be a valid role. See the administration console status messages for more info.", [
325 1 => htmlspecialchars($caseType['name']),
326 2 => htmlspecialchars($role['name']),
327 ]);
328 }
329 // Only two possibilities now are we have a name, or we have a message.
330 // So the if($foundName) is redundant, but seems clearer somehow.
331 if ($foundName && empty($cantConvertMessage)) {
332 // If name and label are the same don't need to update anything.
333 if ($foundName !== $role['name']) {
334 $caseType['definition']['caseRoles'][$roleSequenceId]['name'] = $foundName;
335 $isDirty = TRUE;
336 }
337 }
338 else {
339 $messages[] = $cantConvertMessage;
340 }
341
342 // end looping thru all roles in definition
343 }
344
345 // If this is a dry run during preupgrade checks we can skip this and
346 // just return any messages.
347 // If for real, then update the case type and here if there's errors
348 // we don't really have a choice but to stop the entire upgrade
349 // completely. There's no way to just send back messages during a queue
350 // run. But we can log a message to error log so that the user has a
351 // little more specific info about which case type.
352 if ($isDirty && !$isDryRun) {
353 $exception = NULL;
354 try {
355 $api_result = civicrm_api3('CaseType', 'create', $caseType);
356 }
357 catch (Exception $e) {
358 $exception = $e;
359 $errorMessage = ts("Error updating case type '%1': %2", [
360 1 => htmlspecialchars($caseType['name']),
361 2 => htmlspecialchars($e->getMessage()),
362 ]);
363 CRM_Core_Error::debug_log_message($errorMessage);
364 }
365 if (!empty($api_result['is_error'])) {
366 $errorMessage = ts("Error updating case type '%1': %2", [
367 1 => htmlspecialchars($caseType['name']),
368 2 => htmlspecialchars($api_result['error_message']),
369 ]);
370 CRM_Core_Error::debug_log_message($errorMessage);
371 $exception = new Exception($errorMessage);
372 }
373 // We need to rethrow the error which unfortunately stops the
374 // entire upgrade including any further tasks. But otherwise
375 // the only way to notify the user something went wrong is with a
376 // crazy workaround.
377 if ($exception) {
378 throw $exception;
379 }
380 }
381
382 return $messages;
383 }
384
385 /**
386 * Helper for _processCaseTypeLabelName to check if a label can't be
387 * converted unambiguously to name.
388 *
389 * If it's bidirectional, we can't convert it if there's an edge case
390 * where the two names are different.
391 *
392 * If it's unidirectional, we can't convert it if there's an edge case
393 * where there's another type that has the same label.
394 *
395 * @param $relationshipType array
396 * @param $caseTypeName string
397 * @param $xmlRoleName string
398 *
399 * @return string|NULL
400 */
401 private static function checkAmbiguous($relationshipType, $caseTypeName, $xmlRoleName) {
402 $cantConvertMessage = NULL;
403 if ($relationshipType['label_a_b'] === $relationshipType['label_b_a']) {
404 // bidirectional, so check if names are different for some reason
405 if ($relationshipType['name_a_b'] !== $relationshipType['name_b_a']) {
406 $cantConvertMessage = ts("Case Type '%1', role '%2' has an ambiguous configuration and can't be automatically updated. See the administration console status messages for more info.", [
407 1 => htmlspecialchars($caseTypeName),
408 2 => htmlspecialchars($xmlRoleName),
409 ]);
410 }
411 }
412 else {
413 // Check if it matches either label_a_b or label_b_a for another type
414 foreach (self::$relationshipTypes as $innerLoopId => $innerLoopType) {
415 if ($innerLoopId == $relationshipType['id']) {
416 // Only check types that aren't the same one we're on.
417 // Sidenote: The loop index is integer but the 'id' member is string
418 continue;
419 }
420 if ($innerLoopType['label_a_b'] === $xmlRoleName || $innerLoopType['label_b_a'] === $xmlRoleName) {
421 $cantConvertMessage = ts("Case Type '%1', role '%2' has an ambiguous configuration where the role matches multiple labels and so can't be automatically updated. See the administration console status messages for more info.", [
422 1 => htmlspecialchars($caseTypeName),
423 2 => htmlspecialchars($xmlRoleName),
424 ]);
425 break;
426 }
427 }
428 }
429 return $cantConvertMessage;
430 }
431
187fc9e9 432}