Merge pull request #17588 from artfulrobot/artfulrobot-property-bag-support-empty
[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
ARW
20 */
21class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
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
TO
67 $result = $caseTypeDAO->save();
68 CRM_Case_XMLRepository::singleton()->flush();
69 return $result;
8ffdec17
ARW
70 }
71
cd5823ae
EM
72 /**
73 * Generate and assign an arbitrary value to a field of a test object.
74 *
75 * @param string $fieldName
76 * @param array $fieldDef
77 * @param int $counter
78 * The globally-unique ID of the test object.
79 */
1753bd71 80 protected function assignTestValue($fieldName, &$fieldDef, $counter) {
e547f744 81 if ($fieldName == 'definition') {
1753bd71 82 $this->{$fieldName} = "<CaseType><name>TestCaseType{$counter}</name></CaseType>";
0db6c3e1
TO
83 }
84 else {
1753bd71
TO
85 parent::assignTestValue($fieldName, $fieldDef, $counter);
86 }
87 }
88
20173e0e 89 /**
100fef9d 90 * Format / convert submitted array to xml for case type definition
20173e0e 91 *
10b0bbee 92 * @param string $name
64bd5a0e 93 * @param array $definition
b44e3f84 94 * The case-type definition expressed as an array-tree.
a6c01b45
CW
95 * @return string
96 * XML
20173e0e 97 */
00be9182 98 public static function convertDefinitionToXML($name, $definition) {
a45cd5aa
RLAR
99
100 $xw = new XMLWriter();
101 $xw->openMemory();
102 $xw->setIndent(TRUE);
103 $xw->setIndentString(' ');
104 $xw->startDocument("1.0", 'UTF-8');
105
106 $xw->startElement('CaseType');
107
108 $xw->startElement('name');
109 $xw->text($name);
110 $xw->fullEndElement();
20173e0e 111
466fba66 112 if (array_key_exists('forkable', $definition)) {
a45cd5aa
RLAR
113 $xw->startElement('forkable');
114 $xw->text((int) $definition['forkable']);
115 $xw->fullEndElement();
466fba66
TO
116 }
117
728b5029 118 if (isset($definition['activityTypes'])) {
a45cd5aa
RLAR
119 $xw->startElement('ActivityTypes');
120
10b0bbee 121 foreach ($definition['activityTypes'] as $values) {
a45cd5aa 122 $xw->startElement('ActivityType');
20173e0e 123 foreach ($values as $key => $value) {
a45cd5aa
RLAR
124 $xw->startElement($key);
125 $xw->text($value);
126 $xw->fullEndElement();
20173e0e 127 }
a45cd5aa
RLAR
128 // ActivityType
129 $xw->fullEndElement();
20173e0e 130 }
a45cd5aa
RLAR
131 // ActivityTypes
132 $xw->fullEndElement();
20173e0e 133 }
134
7c2b40d1 135 if (!empty($definition['statuses'])) {
a45cd5aa
RLAR
136 $xw->startElement('Statuses');
137
7c2b40d1 138 foreach ($definition['statuses'] as $value) {
a45cd5aa
RLAR
139 $xw->startElement('Status');
140 $xw->text($value);
141 $xw->fullEndElement();
7c2b40d1 142 }
a45cd5aa
RLAR
143 // Statuses
144 $xw->fullEndElement();
7c2b40d1
CW
145 }
146
728b5029 147 if (isset($definition['activitySets'])) {
a45cd5aa
RLAR
148
149 $xw->startElement('ActivitySets');
10b0bbee 150 foreach ($definition['activitySets'] as $k => $val) {
a45cd5aa
RLAR
151
152 $xw->startElement('ActivitySet');
834bc8e2 153 foreach ($val as $index => $setVal) {
2f58fb09
TO
154 switch ($index) {
155 case 'activityTypes':
156 if (!empty($setVal)) {
a45cd5aa 157 $xw->startElement('ActivityTypes');
2f58fb09 158 foreach ($setVal as $values) {
a45cd5aa 159 $xw->startElement('ActivityType');
2f58fb09 160 foreach ($values as $key => $value) {
a45cd5aa
RLAR
161 // Some parameters here may be arrays of values.
162 // Also, the tests expect an empty array to be represented as an empty value.
163 $value = (array) $value;
164 if (count($value) === 0) {
165 // Create an empty value.
166 $value[] = '';
167 }
168
169 foreach ($value as $val) {
170 $xw->startElement($key);
171 $xw->text($val);
172 $xw->fullEndElement();
173 }
2f58fb09 174 }
a45cd5aa
RLAR
175 // ActivityType
176 $xw->fullEndElement();
834bc8e2 177 }
a45cd5aa
RLAR
178 // ActivityTypes
179 $xw->fullEndElement();
20173e0e 180 }
2f58fb09 181 break;
e547f744 182
5d4fcf54
TO
183 // passthrough
184 case 'sequence':
2f58fb09
TO
185 case 'timeline':
186 if ($setVal) {
a45cd5aa
RLAR
187 $xw->startElement($index);
188 $xw->text('true');
189 $xw->fullEndElement();
2f58fb09
TO
190 }
191 break;
e547f744 192
2f58fb09 193 default:
a45cd5aa
RLAR
194 $xw->startElement($index);
195 $xw->text($setVal);
196 $xw->fullEndElement();
20173e0e 197 }
198 }
a45cd5aa
RLAR
199 // ActivitySet
200 $xw->fullEndElement();
20173e0e 201 }
a45cd5aa
RLAR
202 // ActivitySets
203 $xw->fullEndElement();
20173e0e 204 }
205
728b5029 206 if (isset($definition['caseRoles'])) {
a45cd5aa 207 $xw->startElement('CaseRoles');
10b0bbee 208 foreach ($definition['caseRoles'] as $values) {
a45cd5aa 209 $xw->startElement('RelationshipType');
20173e0e 210 foreach ($values as $key => $value) {
a45cd5aa
RLAR
211 $xw->startElement($key);
212 if ($key == 'groups') {
213 $xw->text(implode(',', (array) $value));
214 }
215 else {
216 $xw->text($value);
217 }
218 // $key
219 $xw->fullEndElement();
20173e0e 220 }
a45cd5aa
RLAR
221 // RelationshipType
222 $xw->fullEndElement();
20173e0e 223 }
a45cd5aa
RLAR
224 // CaseRoles
225 $xw->fullEndElement();
20173e0e 226 }
227
06f21064 228 if (array_key_exists('restrictActivityAsgmtToCmsUser', $definition)) {
a45cd5aa
RLAR
229 $xw->startElement('RestrictActivityAsgmtToCmsUser');
230 $xw->text($definition['restrictActivityAsgmtToCmsUser']);
231 $xw->fullEndElement();
06f21064 232 }
06f21064 233 if (!empty($definition['activityAsgmtGrps'])) {
a45cd5aa 234 $xw->startElement('ActivityAsgmtGrps');
c4bfbde3 235 foreach ((array) $definition['activityAsgmtGrps'] as $value) {
a45cd5aa
RLAR
236 $xw->startElement('Group');
237 $xw->text($value);
238 $xw->fullEndElement();
06f21064 239 }
a45cd5aa
RLAR
240 // ActivityAsgmtGrps
241 $xw->fullEndElement();
06f21064
TO
242 }
243
a45cd5aa
RLAR
244 // CaseType
245 $xw->fullEndElement();
246 $xw->endDocument();
20173e0e 247
a45cd5aa 248 return $xw->outputMemory();
e0d34d59
TO
249 }
250
20173e0e 251 /**
d2e5d2ce 252 * Get the case definition either from db or read from xml file.
20173e0e 253 *
64bd5a0e
TO
254 * @param SimpleXmlElement $xml
255 * A single case-type record.
20173e0e 256 *
a6c01b45
CW
257 * @return array
258 * the definition of the case-type, expressed as PHP array-tree
20173e0e 259 */
00be9182 260 public static function convertXmlToDefinition($xml) {
93d47462 261 // build PHP array based on definition
be2fb01f 262 $definition = [];
20173e0e 263
466fba66
TO
264 if (isset($xml->forkable)) {
265 $definition['forkable'] = (int) $xml->forkable;
266 }
267
06f21064
TO
268 if (isset($xml->RestrictActivityAsgmtToCmsUser)) {
269 $definition['restrictActivityAsgmtToCmsUser'] = (int) $xml->RestrictActivityAsgmtToCmsUser;
270 }
271
272 if (isset($xml->ActivityAsgmtGrps)) {
273 $definition['activityAsgmtGrps'] = (array) $xml->ActivityAsgmtGrps->Group;
85148081
CW
274 // Backwards compat - convert group ids to group names if ids are supplied
275 if (array_filter($definition['activityAsgmtGrps'], ['\CRM_Utils_Rule', 'integer']) === $definition['activityAsgmtGrps']) {
276 foreach ($definition['activityAsgmtGrps'] as $idx => $group) {
277 $definition['activityAsgmtGrps'][$idx] = CRM_Core_DAO::getFieldValue('CRM_Contact_BAO_Group', $group);
278 }
279 }
06f21064
TO
280 }
281
20173e0e 282 // set activity types
728b5029 283 if (isset($xml->ActivityTypes)) {
be2fb01f 284 $definition['activityTypes'] = [];
728b5029
TO
285 foreach ($xml->ActivityTypes->ActivityType as $activityTypeXML) {
286 $definition['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE);
287 }
ae195e71 288 }
289
7c2b40d1
CW
290 // set statuses
291 if (isset($xml->Statuses)) {
292 $definition['statuses'] = (array) $xml->Statuses->Status;
293 }
294
728b5029
TO
295 // set activity sets
296 if (isset($xml->ActivitySets)) {
be2fb01f
CW
297 $definition['activitySets'] = [];
298 $definition['timelineActivityTypes'] = [];
093f1cfd 299
728b5029
TO
300 foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) {
301 // parse basic properties
be2fb01f 302 $activitySet = [];
2f58fb09
TO
303 $activitySet['name'] = (string) $activitySetXML->name;
304 $activitySet['label'] = (string) $activitySetXML->label;
305 if ('true' == (string) $activitySetXML->timeline) {
306 $activitySet['timeline'] = 1;
307 }
308 if ('true' == (string) $activitySetXML->sequence) {
309 $activitySet['sequence'] = 1;
310 }
728b5029 311
728b5029 312 if (isset($activitySetXML->ActivityTypes)) {
be2fb01f 313 $activitySet['activityTypes'] = [];
728b5029 314 foreach ($activitySetXML->ActivityTypes->ActivityType as $activityTypeXML) {
093f1cfd
AP
315 $activityType = json_decode(json_encode($activityTypeXML), TRUE);
316 $activitySet['activityTypes'][] = $activityType;
317 if ($activitySetXML->timeline) {
318 $definition['timelineActivityTypes'][] = $activityType;
319 }
728b5029 320 }
ae195e71 321 }
728b5029 322 $definition['activitySets'][] = $activitySet;
ae195e71 323 }
b6e48667 324 }
20173e0e 325
326 // set case roles
728b5029 327 if (isset($xml->CaseRoles)) {
be2fb01f 328 $definition['caseRoles'] = [];
728b5029 329 foreach ($xml->CaseRoles->RelationshipType as $caseRoleXml) {
9c7ffe36
CW
330 $caseRole = json_decode(json_encode($caseRoleXml), TRUE);
331 if (!empty($caseRole['groups'])) {
332 $caseRole['groups'] = explode(',', $caseRole['groups']);
333 }
334 $definition['caseRoles'][] = $caseRole;
728b5029
TO
335 }
336 }
20173e0e 337
93d47462 338 return $definition;
20173e0e 339 }
340
8ffdec17
ARW
341 /**
342 * Given the list of params in the params array, fetch the object
343 * and store the values in the values array
344 *
64bd5a0e
TO
345 * @param array $params
346 * Input parameters to find object.
347 * @param array $values
348 * Output values of the object.
77b97be7 349 *
8ffdec17 350 * @return CRM_Case_BAO_CaseType|null the found object or null
8ffdec17 351 */
00be9182 352 public static function &getValues(&$params, &$values) {
8ffdec17
ARW
353 $caseType = new CRM_Case_BAO_CaseType();
354
355 $caseType->copyValues($params);
356
357 if ($caseType->find(TRUE)) {
358 CRM_Core_DAO::storeValues($caseType, $values);
359 return $caseType;
360 }
361 return NULL;
362 }
363
364 /**
d2e5d2ce 365 * Takes an associative array and creates a case type object.
8ffdec17 366 *
64bd5a0e
TO
367 * @param array $params
368 * (reference ) an assoc array of name/value pairs.
77b97be7 369 *
16b10e64 370 * @return CRM_Case_BAO_CaseType
8ffdec17 371 */
00be9182 372 public static function &create(&$params) {
8ffdec17
ARW
373 $transaction = new CRM_Core_Transaction();
374
375 if (!empty($params['id'])) {
376 CRM_Utils_Hook::pre('edit', 'CaseType', $params['id'], $params);
377 }
378 else {
379 CRM_Utils_Hook::pre('create', 'CaseType', NULL, $params);
380 }
381
382 $caseType = self::add($params);
383
384 if (is_a($caseType, 'CRM_Core_Error')) {
385 $transaction->rollback();
386 return $caseType;
387 }
388
389 if (!empty($params['id'])) {
390 CRM_Utils_Hook::post('edit', 'CaseType', $caseType->id, $case);
391 }
392 else {
393 CRM_Utils_Hook::post('create', 'CaseType', $caseType->id, $case);
394 }
395 $transaction->commit();
10ffff26 396 CRM_Case_XMLRepository::singleton(TRUE);
31c28ed5 397 CRM_Core_OptionGroup::flushAll();
8ffdec17
ARW
398
399 return $caseType;
400 }
401
402 /**
fe482240
EM
403 * Retrieve DB object based on input parameters.
404 *
405 * It also stores all the retrieved values in the default array.
8ffdec17 406 *
64bd5a0e
TO
407 * @param array $params
408 * (reference ) an assoc array of name/value pairs.
409 * @param array $defaults
410 * (reference ) an assoc array to hold the name / value pairs.
8ffdec17 411 * in a hierarchical manner
da6b46f4 412 *
16b10e64 413 * @return CRM_Case_BAO_CaseType
8ffdec17 414 */
00be9182 415 public static function retrieve(&$params, &$defaults) {
8ffdec17
ARW
416 $caseType = CRM_Case_BAO_CaseType::getValues($params, $defaults);
417 return $caseType;
418 }
419
4c6ce474 420 /**
100fef9d 421 * @param int $caseTypeId
4c6ce474 422 *
c490a46a 423 * @throws CRM_Core_Exception
4c6ce474
EM
424 * @return mixed
425 */
00be9182 426 public static function del($caseTypeId) {
8ffdec17
ARW
427 $caseType = new CRM_Case_DAO_CaseType();
428 $caseType->id = $caseTypeId;
10ffff26
TO
429 $refCounts = $caseType->getReferenceCounts();
430 $total = array_sum(CRM_Utils_Array::collect('count', $refCounts));
431 if ($total) {
be2fb01f 432 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]));
10ffff26
TO
433 }
434 $result = $caseType->delete();
435 CRM_Case_XMLRepository::singleton(TRUE);
436 return $result;
8ffdec17 437 }
076f81b6 438
439 /**
440 * Determine if a case-type name is well-formed
441 *
442 * @param string $caseType
443 * @return bool
444 */
00be9182 445 public static function isValidName($caseType) {
e547f744 446 return preg_match('/^[a-zA-Z0-9_]+$/', $caseType);
076f81b6 447 }
32f1c917
TO
448
449 /**
450 * Determine if the case-type has *both* DB and file-based definitions.
451 *
452 * @param int $caseTypeId
72b3a70c
CW
453 * @return bool|null
454 * TRUE if there are *both* DB and file-based definitions
32f1c917 455 */
00be9182 456 public static function isForked($caseTypeId) {
32f1c917
TO
457 $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
458 if ($caseTypeName) {
459 $dbDefinition = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'definition', 'id', TRUE);
460 $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
461 return $fileDefinition && $dbDefinition;
462 }
463 return NULL;
464 }
465
466 /**
467 * Determine if modifications are allowed on the case-type
468 *
469 * @param int $caseTypeId
a6c01b45
CW
470 * @return bool
471 * TRUE if the definition can be modified
32f1c917 472 */
00be9182 473 public static function isForkable($caseTypeId) {
32f1c917
TO
474 $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
475 if ($caseTypeName) {
476 // if file-based definition explicitly disables "forkable" option, then don't allow changes to definition
477 $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
2210eab6 478 if ($fileDefinition && isset($fileDefinition->forkable)) {
e547f744 479 return CRM_Utils_String::strtobool((string) $fileDefinition->forkable);
32f1c917
TO
480 }
481 }
482 return TRUE;
483 }
96025800 484
8ffdec17 485}