CaseType - Use standard delete function which calls hooks
[civicrm-core.git] / CRM / Case / BAO / CaseType.php
CommitLineData
8ffdec17
ARW
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
8ffdec17 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 |
8ffdec17 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
8ffdec17
ARW
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
8ffdec17
ARW
16 */
17
18/**
67d19299 19 * This class contains the functions for Case Type management.
8ffdec17 20 */
11a1236c 21class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType implements \Civi\Test\HookInterface {
8ffdec17
ARW
22
23 /**
d2e5d2ce 24 * Static field for all the case information that we can potentially export.
8ffdec17
ARW
25 *
26 * @var array
8ffdec17 27 */
f157740d 28 public static $_exportableFields = NULL;
8ffdec17
ARW
29
30 /**
d2e5d2ce 31 * Takes an associative array and creates a Case Type object.
8ffdec17
ARW
32 *
33 * the function extract all the params it needs to initialize the create a
34 * case type object. the params array could contain additional unused name/value
35 * pairs
36 *
64bd5a0e
TO
37 * @param array $params
38 * (reference ) an assoc array of name/value pairs.
77b97be7 39 *
c490a46a 40 * @throws CRM_Core_Exception
8ffdec17 41 *
16b10e64 42 * @return CRM_Case_BAO_CaseType
8ffdec17 43 */
00be9182 44 public static function add(&$params) {
8ffdec17 45 $caseTypeDAO = new CRM_Case_DAO_CaseType();
20173e0e 46
c7bccb5f 47 // form the name only if missing: CRM-627
9c1bc317 48 $nameParam = $params['name'] ?? NULL;
c7bccb5f 49 if (!$nameParam && empty($params['id'])) {
50 $params['name'] = CRM_Utils_String::titleToVar($params['title']);
51 }
46ec593d
TO
52
53 // Old case-types (pre-4.5) may keep their ucky names, but new case-types must satisfy isValidName()
54 if (empty($params['id']) && !empty($params['name']) && !CRM_Case_BAO_CaseType::isValidName($params['name'])) {
55 throw new CRM_Core_Exception("Cannot create new case-type with malformed name [{$params['name']}]");
076f81b6 56 }
c7bccb5f 57
32f1c917
TO
58 $caseTypeName = (isset($params['name'])) ? $params['name'] : CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $params['id'], 'name', 'id', TRUE);
59
20173e0e 60 // function to format definition column
c2f2bbe6 61 if (isset($params['definition']) && is_array($params['definition'])) {
32f1c917 62 $params['definition'] = self::convertDefinitionToXML($caseTypeName, $params['definition']);
ba3228d1 63 CRM_Core_ManagedEntities::scheduleReconciliation();
c2f2bbe6 64 }
20173e0e 65
88396939 66 $caseTypeDAO->copyValues($params);
e492e278 67 $result = $caseTypeDAO->save();
e492e278 68 return $result;
8ffdec17
ARW
69 }
70
cd5823ae
EM
71 /**
72 * Generate and assign an arbitrary value to a field of a test object.
73 *
74 * @param string $fieldName
75 * @param array $fieldDef
76 * @param int $counter
77 * The globally-unique ID of the test object.
78 */
1753bd71 79 protected function assignTestValue($fieldName, &$fieldDef, $counter) {
e547f744 80 if ($fieldName == 'definition') {
1753bd71 81 $this->{$fieldName} = "<CaseType><name>TestCaseType{$counter}</name></CaseType>";
0db6c3e1
TO
82 }
83 else {
1753bd71
TO
84 parent::assignTestValue($fieldName, $fieldDef, $counter);
85 }
86 }
87
96f09dda
CW
88 public static function formatOutputDefinition(&$value, $row) {
89 if ($value) {
90 [$xml] = CRM_Utils_XML::parseString($value);
91 $value = $xml ? self::convertXmlToDefinition($xml) : [];
92 }
93 elseif (!empty($row['id']) || !empty($row['name'])) {
94 $caseTypeName = $row['name'] ?? CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $row['id']);
95 $xml = CRM_Case_XMLRepository::singleton()->retrieve($caseTypeName);
96 $value = $xml ? self::convertXmlToDefinition($xml) : [];
97 }
98 }
99
20173e0e 100 /**
100fef9d 101 * Format / convert submitted array to xml for case type definition
20173e0e 102 *
10b0bbee 103 * @param string $name
64bd5a0e 104 * @param array $definition
b44e3f84 105 * The case-type definition expressed as an array-tree.
a6c01b45
CW
106 * @return string
107 * XML
20173e0e 108 */
00be9182 109 public static function convertDefinitionToXML($name, $definition) {
a45cd5aa
RLAR
110
111 $xw = new XMLWriter();
112 $xw->openMemory();
113 $xw->setIndent(TRUE);
114 $xw->setIndentString(' ');
115 $xw->startDocument("1.0", 'UTF-8');
116
117 $xw->startElement('CaseType');
118
119 $xw->startElement('name');
120 $xw->text($name);
121 $xw->fullEndElement();
20173e0e 122
466fba66 123 if (array_key_exists('forkable', $definition)) {
a45cd5aa
RLAR
124 $xw->startElement('forkable');
125 $xw->text((int) $definition['forkable']);
126 $xw->fullEndElement();
466fba66
TO
127 }
128
728b5029 129 if (isset($definition['activityTypes'])) {
a45cd5aa
RLAR
130 $xw->startElement('ActivityTypes');
131
10b0bbee 132 foreach ($definition['activityTypes'] as $values) {
a45cd5aa 133 $xw->startElement('ActivityType');
20173e0e 134 foreach ($values as $key => $value) {
a45cd5aa
RLAR
135 $xw->startElement($key);
136 $xw->text($value);
137 $xw->fullEndElement();
20173e0e 138 }
a45cd5aa
RLAR
139 // ActivityType
140 $xw->fullEndElement();
20173e0e 141 }
a45cd5aa
RLAR
142 // ActivityTypes
143 $xw->fullEndElement();
20173e0e 144 }
145
7c2b40d1 146 if (!empty($definition['statuses'])) {
a45cd5aa
RLAR
147 $xw->startElement('Statuses');
148
7c2b40d1 149 foreach ($definition['statuses'] as $value) {
a45cd5aa
RLAR
150 $xw->startElement('Status');
151 $xw->text($value);
152 $xw->fullEndElement();
7c2b40d1 153 }
a45cd5aa
RLAR
154 // Statuses
155 $xw->fullEndElement();
7c2b40d1
CW
156 }
157
728b5029 158 if (isset($definition['activitySets'])) {
a45cd5aa
RLAR
159
160 $xw->startElement('ActivitySets');
10b0bbee 161 foreach ($definition['activitySets'] as $k => $val) {
a45cd5aa
RLAR
162
163 $xw->startElement('ActivitySet');
834bc8e2 164 foreach ($val as $index => $setVal) {
2f58fb09
TO
165 switch ($index) {
166 case 'activityTypes':
167 if (!empty($setVal)) {
a45cd5aa 168 $xw->startElement('ActivityTypes');
2f58fb09 169 foreach ($setVal as $values) {
a45cd5aa 170 $xw->startElement('ActivityType');
2f58fb09 171 foreach ($values as $key => $value) {
a45cd5aa
RLAR
172 // Some parameters here may be arrays of values.
173 // Also, the tests expect an empty array to be represented as an empty value.
174 $value = (array) $value;
175 if (count($value) === 0) {
176 // Create an empty value.
177 $value[] = '';
178 }
179
180 foreach ($value as $val) {
181 $xw->startElement($key);
182 $xw->text($val);
183 $xw->fullEndElement();
184 }
2f58fb09 185 }
a45cd5aa
RLAR
186 // ActivityType
187 $xw->fullEndElement();
834bc8e2 188 }
a45cd5aa
RLAR
189 // ActivityTypes
190 $xw->fullEndElement();
20173e0e 191 }
2f58fb09 192 break;
e547f744 193
5d4fcf54
TO
194 // passthrough
195 case 'sequence':
2f58fb09
TO
196 case 'timeline':
197 if ($setVal) {
a45cd5aa
RLAR
198 $xw->startElement($index);
199 $xw->text('true');
200 $xw->fullEndElement();
2f58fb09
TO
201 }
202 break;
e547f744 203
2f58fb09 204 default:
a45cd5aa
RLAR
205 $xw->startElement($index);
206 $xw->text($setVal);
207 $xw->fullEndElement();
20173e0e 208 }
209 }
a45cd5aa
RLAR
210 // ActivitySet
211 $xw->fullEndElement();
20173e0e 212 }
a45cd5aa
RLAR
213 // ActivitySets
214 $xw->fullEndElement();
20173e0e 215 }
216
728b5029 217 if (isset($definition['caseRoles'])) {
a45cd5aa 218 $xw->startElement('CaseRoles');
10b0bbee 219 foreach ($definition['caseRoles'] as $values) {
a45cd5aa 220 $xw->startElement('RelationshipType');
20173e0e 221 foreach ($values as $key => $value) {
a45cd5aa
RLAR
222 $xw->startElement($key);
223 if ($key == 'groups') {
224 $xw->text(implode(',', (array) $value));
225 }
226 else {
227 $xw->text($value);
228 }
229 // $key
230 $xw->fullEndElement();
20173e0e 231 }
a45cd5aa
RLAR
232 // RelationshipType
233 $xw->fullEndElement();
20173e0e 234 }
a45cd5aa
RLAR
235 // CaseRoles
236 $xw->fullEndElement();
20173e0e 237 }
238
06f21064 239 if (array_key_exists('restrictActivityAsgmtToCmsUser', $definition)) {
a45cd5aa
RLAR
240 $xw->startElement('RestrictActivityAsgmtToCmsUser');
241 $xw->text($definition['restrictActivityAsgmtToCmsUser']);
242 $xw->fullEndElement();
06f21064 243 }
06f21064 244 if (!empty($definition['activityAsgmtGrps'])) {
a45cd5aa 245 $xw->startElement('ActivityAsgmtGrps');
c4bfbde3 246 foreach ((array) $definition['activityAsgmtGrps'] as $value) {
a45cd5aa
RLAR
247 $xw->startElement('Group');
248 $xw->text($value);
249 $xw->fullEndElement();
06f21064 250 }
a45cd5aa
RLAR
251 // ActivityAsgmtGrps
252 $xw->fullEndElement();
06f21064
TO
253 }
254
a45cd5aa
RLAR
255 // CaseType
256 $xw->fullEndElement();
257 $xw->endDocument();
20173e0e 258
a45cd5aa 259 return $xw->outputMemory();
e0d34d59
TO
260 }
261
20173e0e 262 /**
d2e5d2ce 263 * Get the case definition either from db or read from xml file.
20173e0e 264 *
64bd5a0e
TO
265 * @param SimpleXmlElement $xml
266 * A single case-type record.
20173e0e 267 *
a6c01b45
CW
268 * @return array
269 * the definition of the case-type, expressed as PHP array-tree
20173e0e 270 */
00be9182 271 public static function convertXmlToDefinition($xml) {
93d47462 272 // build PHP array based on definition
be2fb01f 273 $definition = [];
20173e0e 274
466fba66
TO
275 if (isset($xml->forkable)) {
276 $definition['forkable'] = (int) $xml->forkable;
277 }
278
06f21064
TO
279 if (isset($xml->RestrictActivityAsgmtToCmsUser)) {
280 $definition['restrictActivityAsgmtToCmsUser'] = (int) $xml->RestrictActivityAsgmtToCmsUser;
281 }
282
283 if (isset($xml->ActivityAsgmtGrps)) {
284 $definition['activityAsgmtGrps'] = (array) $xml->ActivityAsgmtGrps->Group;
85148081
CW
285 // Backwards compat - convert group ids to group names if ids are supplied
286 if (array_filter($definition['activityAsgmtGrps'], ['\CRM_Utils_Rule', 'integer']) === $definition['activityAsgmtGrps']) {
287 foreach ($definition['activityAsgmtGrps'] as $idx => $group) {
288 $definition['activityAsgmtGrps'][$idx] = CRM_Core_DAO::getFieldValue('CRM_Contact_BAO_Group', $group);
289 }
290 }
06f21064
TO
291 }
292
20173e0e 293 // set activity types
728b5029 294 if (isset($xml->ActivityTypes)) {
be2fb01f 295 $definition['activityTypes'] = [];
728b5029
TO
296 foreach ($xml->ActivityTypes->ActivityType as $activityTypeXML) {
297 $definition['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE);
298 }
ae195e71 299 }
300
7c2b40d1
CW
301 // set statuses
302 if (isset($xml->Statuses)) {
303 $definition['statuses'] = (array) $xml->Statuses->Status;
304 }
305
728b5029
TO
306 // set activity sets
307 if (isset($xml->ActivitySets)) {
be2fb01f
CW
308 $definition['activitySets'] = [];
309 $definition['timelineActivityTypes'] = [];
093f1cfd 310
728b5029
TO
311 foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) {
312 // parse basic properties
be2fb01f 313 $activitySet = [];
2f58fb09
TO
314 $activitySet['name'] = (string) $activitySetXML->name;
315 $activitySet['label'] = (string) $activitySetXML->label;
316 if ('true' == (string) $activitySetXML->timeline) {
317 $activitySet['timeline'] = 1;
318 }
319 if ('true' == (string) $activitySetXML->sequence) {
320 $activitySet['sequence'] = 1;
321 }
728b5029 322
728b5029 323 if (isset($activitySetXML->ActivityTypes)) {
be2fb01f 324 $activitySet['activityTypes'] = [];
728b5029 325 foreach ($activitySetXML->ActivityTypes->ActivityType as $activityTypeXML) {
093f1cfd
AP
326 $activityType = json_decode(json_encode($activityTypeXML), TRUE);
327 $activitySet['activityTypes'][] = $activityType;
328 if ($activitySetXML->timeline) {
329 $definition['timelineActivityTypes'][] = $activityType;
330 }
728b5029 331 }
ae195e71 332 }
728b5029 333 $definition['activitySets'][] = $activitySet;
ae195e71 334 }
b6e48667 335 }
20173e0e 336
337 // set case roles
728b5029 338 if (isset($xml->CaseRoles)) {
be2fb01f 339 $definition['caseRoles'] = [];
728b5029 340 foreach ($xml->CaseRoles->RelationshipType as $caseRoleXml) {
9c7ffe36
CW
341 $caseRole = json_decode(json_encode($caseRoleXml), TRUE);
342 if (!empty($caseRole['groups'])) {
343 $caseRole['groups'] = explode(',', $caseRole['groups']);
344 }
345 $definition['caseRoles'][] = $caseRole;
728b5029
TO
346 }
347 }
20173e0e 348
93d47462 349 return $definition;
20173e0e 350 }
351
8ffdec17
ARW
352 /**
353 * Given the list of params in the params array, fetch the object
354 * and store the values in the values array
355 *
64bd5a0e
TO
356 * @param array $params
357 * Input parameters to find object.
358 * @param array $values
359 * Output values of the object.
77b97be7 360 *
8ffdec17 361 * @return CRM_Case_BAO_CaseType|null the found object or null
8ffdec17 362 */
00be9182 363 public static function &getValues(&$params, &$values) {
8ffdec17
ARW
364 $caseType = new CRM_Case_BAO_CaseType();
365
366 $caseType->copyValues($params);
367
368 if ($caseType->find(TRUE)) {
369 CRM_Core_DAO::storeValues($caseType, $values);
370 return $caseType;
371 }
372 return NULL;
373 }
374
375 /**
d2e5d2ce 376 * Takes an associative array and creates a case type object.
8ffdec17 377 *
64bd5a0e
TO
378 * @param array $params
379 * (reference ) an assoc array of name/value pairs.
77b97be7 380 *
16b10e64 381 * @return CRM_Case_BAO_CaseType
8ffdec17 382 */
00be9182 383 public static function &create(&$params) {
8ffdec17 384 $transaction = new CRM_Core_Transaction();
96f09dda
CW
385 // Computed properties.
386 unset($params['is_forkable']);
387 unset($params['is_forked']);
388
389 $action = empty($params['id']) ? 'create' : 'edit';
390
391 CRM_Utils_Hook::pre($action, 'CaseType', $params['id'] ?? NULL, $params);
392
393 // This is an existing case-type.
394 if ($action === 'edit' && isset($params['definition'])
395 // which is not yet forked
396 && !self::isForked($params['id'])
397 // for which new forks are prohibited
398 && !self::isForkable($params['id'])
399 ) {
400 unset($params['definition']);
8ffdec17
ARW
401 }
402
403 $caseType = self::add($params);
404
405 if (is_a($caseType, 'CRM_Core_Error')) {
406 $transaction->rollback();
407 return $caseType;
408 }
11a1236c 409 $transaction->commit();
8ffdec17 410
96f09dda
CW
411 CRM_Utils_Hook::post($action, 'CaseType', $caseType->id, $case);
412
8ffdec17
ARW
413 return $caseType;
414 }
415
416 /**
fe482240
EM
417 * Retrieve DB object based on input parameters.
418 *
419 * It also stores all the retrieved values in the default array.
8ffdec17 420 *
64bd5a0e
TO
421 * @param array $params
422 * (reference ) an assoc array of name/value pairs.
423 * @param array $defaults
424 * (reference ) an assoc array to hold the name / value pairs.
8ffdec17 425 * in a hierarchical manner
da6b46f4 426 *
16b10e64 427 * @return CRM_Case_BAO_CaseType
8ffdec17 428 */
00be9182 429 public static function retrieve(&$params, &$defaults) {
8ffdec17
ARW
430 $caseType = CRM_Case_BAO_CaseType::getValues($params, $defaults);
431 return $caseType;
432 }
433
4c6ce474 434 /**
100fef9d 435 * @param int $caseTypeId
4c6ce474 436 *
11a1236c 437 * @deprecated
c490a46a 438 * @throws CRM_Core_Exception
11a1236c 439 * @return CRM_Case_DAO_CaseType
4c6ce474 440 */
00be9182 441 public static function del($caseTypeId) {
11a1236c
CW
442 return static::deleteRecord(['id' => $caseTypeId]);
443 }
444
445 /**
446 * Callback for hook_civicrm_pre().
447 * @param \Civi\Core\Event\PreEvent $event
448 * @throws CRM_Core_Exception
449 */
450 public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) {
451 // Before deleting a caseType, check references
452 if ($event->action === 'delete') {
453 $caseType = new CRM_Case_DAO_CaseType();
454 $caseType->id = $event->id;
455 $refCounts = $caseType->getReferenceCounts();
456 $total = array_sum(CRM_Utils_Array::collect('count', $refCounts));
457 if (array_sum(CRM_Utils_Array::collect('count', $refCounts))) {
458 throw new CRM_Core_Exception(ts("You can not delete this case type -- it is assigned to %1 existing case record(s). If you do not want this case type to be used going forward, consider disabling it instead.", [1 => $total]));
459 }
10ffff26 460 }
11a1236c
CW
461 }
462
463 /**
464 * Callback for hook_civicrm_post().
465 * @param \Civi\Core\Event\PostEvent $event
466 */
467 public static function self_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) {
468 // When a caseType is saved or deleted, flush xml and optionGroup cache
469 CRM_Case_XMLRepository::singleton()->flush();
470 CRM_Core_OptionGroup::flushAll();
8ffdec17 471 }
076f81b6 472
473 /**
474 * Determine if a case-type name is well-formed
475 *
476 * @param string $caseType
477 * @return bool
478 */
00be9182 479 public static function isValidName($caseType) {
e547f744 480 return preg_match('/^[a-zA-Z0-9_]+$/', $caseType);
076f81b6 481 }
32f1c917
TO
482
483 /**
484 * Determine if the case-type has *both* DB and file-based definitions.
485 *
486 * @param int $caseTypeId
72b3a70c
CW
487 * @return bool|null
488 * TRUE if there are *both* DB and file-based definitions
32f1c917 489 */
00be9182 490 public static function isForked($caseTypeId) {
32f1c917
TO
491 $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
492 if ($caseTypeName) {
493 $dbDefinition = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'definition', 'id', TRUE);
494 $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
495 return $fileDefinition && $dbDefinition;
496 }
497 return NULL;
498 }
499
500 /**
501 * Determine if modifications are allowed on the case-type
502 *
503 * @param int $caseTypeId
a6c01b45
CW
504 * @return bool
505 * TRUE if the definition can be modified
32f1c917 506 */
00be9182 507 public static function isForkable($caseTypeId) {
32f1c917
TO
508 $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
509 if ($caseTypeName) {
510 // if file-based definition explicitly disables "forkable" option, then don't allow changes to definition
511 $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
2210eab6 512 if ($fileDefinition && isset($fileDefinition->forkable)) {
e547f744 513 return CRM_Utils_String::strtobool((string) $fileDefinition->forkable);
32f1c917
TO
514 }
515 }
516 return TRUE;
517 }
96025800 518
8ffdec17 519}