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