Commit | Line | Data |
---|---|---|
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 | */ |
21 | class 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 | } |