From 400db156466ea149b6089c54e9feaed9180bf3fd Mon Sep 17 00:00:00 2001 From: DemeritCowboy Date: Fri, 18 Oct 2019 12:31:17 -0400 Subject: [PATCH] update script to flip the autoassignee direction --- CRM/Upgrade/Incremental/php/FiveTwenty.php | 100 +++++++++ .../Incremental/php/FiveTwentyTest.php | 194 ++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 tests/phpunit/CRM/Upgrade/Incremental/php/FiveTwentyTest.php diff --git a/CRM/Upgrade/Incremental/php/FiveTwenty.php b/CRM/Upgrade/Incremental/php/FiveTwenty.php index 9485ff8813..ac9ccbfc68 100644 --- a/CRM/Upgrade/Incremental/php/FiveTwenty.php +++ b/CRM/Upgrade/Incremental/php/FiveTwenty.php @@ -28,6 +28,12 @@ * Upgrade logic for FiveTwenty */ class CRM_Upgrade_Incremental_php_FiveTwenty extends CRM_Upgrade_Incremental_Base { + /** + * @var $relationshipTypes array + * api call result keyed on relationship_type.id + */ + protected static $relationshipTypes; + /** * Compute any messages which should be displayed beforeupgrade. * @@ -83,7 +89,101 @@ class CRM_Upgrade_Incremental_php_FiveTwenty extends CRM_Upgrade_Incremental_Bas "tinyint(4) DEFAULT '0' COMMENT 'Shows this is a template for recurring contributions.'", FALSE, '5.20.alpha1'); $this->addTask('Add order_reference field to civicrm_financial_trxn', 'addColumn', 'civicrm_financial_trxn', 'order_reference', "varchar(255) COMMENT 'Payment Processor external order reference'", FALSE, '5.20.alpha1'); + $config = CRM_Core_Config::singleton(); + if (in_array('CiviCase', $config->enableComponents)) { + $this->addTask('Change direction of autoassignees in case type xml', 'changeCaseTypeAutoassignee'); + } $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); } + /** + * Change direction of activity autoassignees in case type xml for + * bidirectional relationship types if they point the other way. This is + * mostly a visual issue on the case type edit screen and doesn't affect + * normal operation, but could lead to confusion and a future mixup. + * (dev/core#1046) + * ONLY for ones using database storage - don't want to "fork" case types + * that aren't currently forked. + * + * Earlier iterations of this used the api and array manipulation + * and then another iteration used SimpleXML manipulation, but both + * suffered from weirdnesses in how conversion back and forth worked. + * + * Here we use SQL and a regex. The thing we're changing is pretty + * well-defined and unique: + * N_b_a + * + * @return bool + */ + public static function changeCaseTypeAutoassignee() { + self::$relationshipTypes = civicrm_api3('RelationshipType', 'get', [ + 'options' => ['limit' => 0], + ])['values']; + + // Get all case types definitions that are using db storage + $dao = CRM_Core_DAO::executeQuery("SELECT id, definition FROM civicrm_case_type WHERE definition IS NOT NULL AND definition <> ''"); + while ($dao->fetch()) { + self::processCaseTypeAutoassignee($dao->id, $dao->definition); + } + return TRUE; + } + + /** + * Process a single case type + * + * @param $caseTypeId int + * @param $definition string + * xml string + */ + public static function processCaseTypeAutoassignee($caseTypeId, $definition) { + $isDirty = FALSE; + // find the autoassignees + preg_match_all('/(.*?)<\/default_assignee_relationship>/', $definition, $matches); + // $matches[1][n] has the text inside the xml tag, e.g. 2_a_b + foreach ($matches[1] as $index => $match) { + if (empty($match)) { + continue; + } + // parse out existing id and direction + list($relationshipTypeId, $direction1) = explode('_', $match); + // we only care about ones that are b_a + if ($direction1 === 'b') { + // we only care about bidirectional + if (self::isBidirectionalRelationship($relationshipTypeId)) { + // flip it to be a_b + // $matches[0][n] has the whole match including the xml tag + $definition = str_replace($matches[0][$index], "{$relationshipTypeId}_a_b", $definition); + $isDirty = TRUE; + } + } + } + + if ($isDirty) { + $sqlParams = [ + 1 => [$definition, 'String'], + 2 => [$caseTypeId, 'Integer'], + ]; + CRM_Core_DAO::executeQuery("UPDATE civicrm_case_type SET definition = %1 WHERE id = %2", $sqlParams); + //echo "UPDATE civicrm_case_type SET definition = '" . CRM_Core_DAO::escapeString($sqlParams[1][0]) . "' WHERE id = {$sqlParams[2][0]}\n"; + } + } + + /** + * Check if this is bidirectional, based on label. In the situation where + * we're using this we don't care too much about the edge case where name + * might not also be bidirectional. + * + * @param $relationshipTypeId int + * + * @return bool + */ + private static function isBidirectionalRelationship($relationshipTypeId) { + if (isset(self::$relationshipTypes[$relationshipTypeId])) { + if (self::$relationshipTypes[$relationshipTypeId]['label_a_b'] === self::$relationshipTypes[$relationshipTypeId]['label_b_a']) { + return TRUE; + } + } + return FALSE; + } + } diff --git a/tests/phpunit/CRM/Upgrade/Incremental/php/FiveTwentyTest.php b/tests/phpunit/CRM/Upgrade/Incremental/php/FiveTwentyTest.php new file mode 100644 index 0000000000..5cbf00f785 --- /dev/null +++ b/tests/phpunit/CRM/Upgrade/Incremental/php/FiveTwentyTest.php @@ -0,0 +1,194 @@ +callAPISuccess('RelationshipType', 'get', ['limit' => 0])['values']; + // Get list of ids keyed on name. + $relationshipTypeNames = array_column($result, 'id', 'name_b_a'); + + // Create spouse if none. + if (!isset($relationshipTypeNames['Spouse of'])) { + $spouseId = $this->relationshipTypeCreate([ + 'name_a_b' => 'Spouse of', + 'name_b_a' => 'Spouse of', + 'label_a_b' => 'Spouse of', + 'label_b_a' => 'Spouse of', + 'contact_type_a' => 'Individual', + 'contact_type_b' => 'Individual', + ]); + $relationshipTypeNames['Spouse of'] = $spouseId; + } + // Maybe unnecessary but why not. Slightly different than an undefined + // index later when it doesn't exist at all. + $this->assertGreaterThan(0, $relationshipTypeNames['Spouse of']); + + /** + * Set up xml. + * In this sample case type for autoassignees we have: + * - a_b unidirectional: Case Coordinator is + * - b_a unidirectional: Benefits Specialist + * - Bidirectional the way it's stored in 5.16+: Spouse of + * - config entry pre-5.16, where it's a bidirectional relationship stored as b_a: Spouse of + * + * Also just for extra some non-ascii chars in here to show they + * don't get borked. + */ + $newCaseTypeXml = << + + +test_type + + +Open Case +1 + + +Email + + +Follow up + + +Meeting + + +Phone Call + + +давид + + + +standard_timeline + +true + + +Open Case +Completed + +1 + + + + +timeline_1 + +true + + +Follow up + +Scheduled +Open Case +7 +newest +2 +{$relationshipTypeNames['Senior Services Coordinator']}_b_a + + + +Follow up + +Scheduled +Open Case +14 +newest +2 +{$relationshipTypeNames['Benefits Specialist']}_a_b + + + +Follow up + +Scheduled +Open Case +21 +newest +2 +{$relationshipTypeNames['Spouse of']}_a_b + + + +Follow up + +Scheduled +Open Case +28 +newest +2 +{$relationshipTypeNames['Spouse of']}_b_a + + + + + + + +Senior Services Coordinator +1 +1 + + +Spouse of + + +Benefits Specialist is + + +0 + +ENDXML; + + $dao = new CRM_Case_DAO_CaseType(); + $dao->name = 'test_type'; + $dao->title = 'Test Type'; + $dao->is_active = 1; + $dao->definition = $newCaseTypeXml; + $dao->insert(); + + $caseTypeId = $dao->id; + + // run the task + $upgrader = new CRM_Upgrade_Incremental_php_FiveTwenty(); + $upgrader->changeCaseTypeAutoassignee(); + + // Check if the case type is what we expect. It should be identical except + // the b_a spouse one should get converted to the a_b direction. + $expectedCaseTypeXml = str_replace( + "{$relationshipTypeNames['Spouse of']}_b_a", + "{$relationshipTypeNames['Spouse of']}_a_b", + $newCaseTypeXml + ); + + //echo $expectedCaseTypeXml; + + // Get the updated case type and check. + $dao = CRM_Core_DAO::executeQuery("SELECT definition FROM civicrm_case_type WHERE id = {$caseTypeId}"); + $dao->fetch(); + + $this->assertEquals($expectedCaseTypeXml, $dao->definition); + } + +} -- 2.25.1