3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * This class contains the functions for Case Type management.
21 class CRM_Case_BAO_CaseType
extends CRM_Case_DAO_CaseType
implements \Civi\Core\HookInterface
{
24 * Static field for all the case information that we can potentially export.
28 public static $_exportableFields = NULL;
31 * Takes an associative array and creates a Case Type object.
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
37 * @param array $params
38 * (reference ) an assoc array of name/value pairs.
40 * @throws CRM_Core_Exception
42 * @return CRM_Case_BAO_CaseType
44 public static function add(&$params) {
45 $caseTypeDAO = new CRM_Case_DAO_CaseType();
47 // form the name only if missing: CRM-627
48 $nameParam = $params['name'] ??
NULL;
49 if (!$nameParam && empty($params['id'])) {
50 $params['name'] = CRM_Utils_String
::titleToVar($params['title']);
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']}]");
58 $caseTypeName = (isset($params['name'])) ?
$params['name'] : CRM_Core_DAO
::getFieldValue('CRM_Case_DAO_CaseType', $params['id'], 'name', 'id', TRUE);
60 // function to format definition column
61 if (isset($params['definition']) && is_array($params['definition'])) {
62 $params['definition'] = self
::convertDefinitionToXML($caseTypeName, $params['definition']);
63 CRM_Core_ManagedEntities
::scheduleReconciliation();
66 $caseTypeDAO->copyValues($params);
67 $result = $caseTypeDAO->save();
72 * Generate and assign an arbitrary value to a field of a test object.
74 * @param string $fieldName
75 * @param array $fieldDef
77 * The globally-unique ID of the test object.
79 protected function assignTestValue($fieldName, &$fieldDef, $counter) {
80 if ($fieldName == 'definition') {
81 $this->{$fieldName} = "<CaseType><name>TestCaseType{$counter}</name></CaseType>";
84 parent
::assignTestValue($fieldName, $fieldDef, $counter);
88 public static function formatOutputDefinition(&$value, $row) {
90 [$xml] = CRM_Utils_XML
::parseString($value);
91 $value = $xml ? self
::convertXmlToDefinition($xml) : [];
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) : [];
101 * Format / convert submitted array to xml for case type definition
103 * @param string $name
104 * @param array $definition
105 * The case-type definition expressed as an array-tree.
109 public static function convertDefinitionToXML($name, $definition) {
111 $xw = new XMLWriter();
113 $xw->setIndent(TRUE);
114 $xw->setIndentString(' ');
115 $xw->startDocument("1.0", 'UTF-8');
117 $xw->startElement('CaseType');
119 $xw->startElement('name');
121 $xw->fullEndElement();
123 if (array_key_exists('forkable', $definition)) {
124 $xw->startElement('forkable');
125 $xw->text((int) $definition['forkable']);
126 $xw->fullEndElement();
129 if (isset($definition['activityTypes'])) {
130 $xw->startElement('ActivityTypes');
132 foreach ($definition['activityTypes'] as $values) {
133 $xw->startElement('ActivityType');
134 foreach ($values as $key => $value) {
135 $xw->startElement($key);
137 $xw->fullEndElement();
140 $xw->fullEndElement();
143 $xw->fullEndElement();
146 if (!empty($definition['statuses'])) {
147 $xw->startElement('Statuses');
149 foreach ($definition['statuses'] as $value) {
150 $xw->startElement('Status');
152 $xw->fullEndElement();
155 $xw->fullEndElement();
158 if (isset($definition['activitySets'])) {
160 $xw->startElement('ActivitySets');
161 foreach ($definition['activitySets'] as $k => $val) {
163 $xw->startElement('ActivitySet');
164 foreach ($val as $index => $setVal) {
166 case 'activityTypes':
167 if (!empty($setVal)) {
168 $xw->startElement('ActivityTypes');
169 foreach ($setVal as $values) {
170 $xw->startElement('ActivityType');
171 foreach ($values as $key => $value) {
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.
180 foreach ($value as $val) {
181 $xw->startElement($key);
183 $xw->fullEndElement();
187 $xw->fullEndElement();
190 $xw->fullEndElement();
198 $xw->startElement($index);
200 $xw->fullEndElement();
205 $xw->startElement($index);
207 $xw->fullEndElement();
211 $xw->fullEndElement();
214 $xw->fullEndElement();
217 if (isset($definition['caseRoles'])) {
218 $xw->startElement('CaseRoles');
219 foreach ($definition['caseRoles'] as $values) {
220 $xw->startElement('RelationshipType');
221 foreach ($values as $key => $value) {
222 $xw->startElement($key);
223 if ($key == 'groups') {
224 $xw->text(implode(',', (array) $value));
230 $xw->fullEndElement();
233 $xw->fullEndElement();
236 $xw->fullEndElement();
239 if (array_key_exists('restrictActivityAsgmtToCmsUser', $definition)) {
240 $xw->startElement('RestrictActivityAsgmtToCmsUser');
241 $xw->text($definition['restrictActivityAsgmtToCmsUser']);
242 $xw->fullEndElement();
244 if (!empty($definition['activityAsgmtGrps'])) {
245 $xw->startElement('ActivityAsgmtGrps');
246 foreach ((array) $definition['activityAsgmtGrps'] as $value) {
247 $xw->startElement('Group');
249 $xw->fullEndElement();
252 $xw->fullEndElement();
256 $xw->fullEndElement();
259 return $xw->outputMemory();
263 * Get the case definition either from db or read from xml file.
265 * @param SimpleXmlElement $xml
266 * A single case-type record.
269 * the definition of the case-type, expressed as PHP array-tree
271 public static function convertXmlToDefinition($xml) {
272 // build PHP array based on definition
275 if (isset($xml->forkable
)) {
276 $definition['forkable'] = (int) $xml->forkable
;
279 if (isset($xml->RestrictActivityAsgmtToCmsUser
)) {
280 $definition['restrictActivityAsgmtToCmsUser'] = (int) $xml->RestrictActivityAsgmtToCmsUser
;
283 if (isset($xml->ActivityAsgmtGrps
)) {
284 $definition['activityAsgmtGrps'] = (array) $xml->ActivityAsgmtGrps
->Group
;
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);
293 // set activity types
294 if (isset($xml->ActivityTypes
)) {
295 $definition['activityTypes'] = [];
296 foreach ($xml->ActivityTypes
->ActivityType
as $activityTypeXML) {
297 $definition['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE);
302 if (isset($xml->Statuses
)) {
303 $definition['statuses'] = (array) $xml->Statuses
->Status
;
307 if (isset($xml->ActivitySets
)) {
308 $definition['activitySets'] = [];
309 $definition['timelineActivityTypes'] = [];
311 foreach ($xml->ActivitySets
->ActivitySet
as $activitySetXML) {
312 // parse basic properties
314 $activitySet['name'] = (string) $activitySetXML->name
;
315 $activitySet['label'] = (string) $activitySetXML->label
;
316 if ('true' == (string) $activitySetXML->timeline
) {
317 $activitySet['timeline'] = 1;
319 if ('true' == (string) $activitySetXML->sequence
) {
320 $activitySet['sequence'] = 1;
323 if (isset($activitySetXML->ActivityTypes
)) {
324 $activitySet['activityTypes'] = [];
325 foreach ($activitySetXML->ActivityTypes
->ActivityType
as $activityTypeXML) {
326 $activityType = json_decode(json_encode($activityTypeXML), TRUE);
327 $activitySet['activityTypes'][] = $activityType;
328 if ($activitySetXML->timeline
) {
329 $definition['timelineActivityTypes'][] = $activityType;
333 $definition['activitySets'][] = $activitySet;
338 if (isset($xml->CaseRoles
)) {
339 $definition['caseRoles'] = [];
340 foreach ($xml->CaseRoles
->RelationshipType
as $caseRoleXml) {
341 $caseRole = json_decode(json_encode($caseRoleXml), TRUE);
342 if (!empty($caseRole['groups'])) {
343 $caseRole['groups'] = explode(',', $caseRole['groups']);
345 $definition['caseRoles'][] = $caseRole;
353 * Given the list of params in the params array, fetch the object
354 * and store the values in the values array
356 * @param array $params
357 * Input parameters to find object.
358 * @param array $values
359 * Output values of the object.
361 * @return CRM_Case_BAO_CaseType|null the found object or null
363 public static function &getValues(&$params, &$values) {
364 $caseType = new CRM_Case_BAO_CaseType();
366 $caseType->copyValues($params);
368 if ($caseType->find(TRUE)) {
369 CRM_Core_DAO
::storeValues($caseType, $values);
376 * Takes an associative array and creates a case type object.
378 * @param array $params
379 * (reference ) an assoc array of name/value pairs.
381 * @return CRM_Case_BAO_CaseType
383 public static function &create(&$params) {
384 $transaction = new CRM_Core_Transaction();
385 // Computed properties.
386 unset($params['is_forkable']);
387 unset($params['is_forked']);
389 $action = empty($params['id']) ?
'create' : 'edit';
391 CRM_Utils_Hook
::pre($action, 'CaseType', $params['id'] ??
NULL, $params);
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'])
400 unset($params['definition']);
403 $caseType = self
::add($params);
405 if (is_a($caseType, 'CRM_Core_Error')) {
406 $transaction->rollback();
409 $transaction->commit();
411 CRM_Utils_Hook
::post($action, 'CaseType', $caseType->id
, $case);
417 * Retrieve DB object based on input parameters.
419 * It also stores all the retrieved values in the default array.
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.
425 * in a hierarchical manner
427 * @return CRM_Case_BAO_CaseType
429 public static function retrieve(&$params, &$defaults) {
430 $caseType = CRM_Case_BAO_CaseType
::getValues($params, $defaults);
435 * @param int $caseTypeId
438 * @throws CRM_Core_Exception
439 * @return CRM_Case_DAO_CaseType
441 public static function del($caseTypeId) {
442 return static::deleteRecord(['id' => $caseTypeId]);
446 * Callback for hook_civicrm_pre().
447 * @param \Civi\Core\Event\PreEvent $event
448 * @throws CRM_Core_Exception
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]));
464 * Callback for hook_civicrm_post().
465 * @param \Civi\Core\Event\PostEvent $event
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();
474 * Determine if a case-type name is well-formed
476 * @param string $caseType
479 public static function isValidName($caseType) {
480 return preg_match('/^[a-zA-Z0-9_]+$/', $caseType);
484 * Determine if the case-type has *both* DB and file-based definitions.
486 * @param int $caseTypeId
488 * TRUE if there are *both* DB and file-based definitions
490 public static function isForked($caseTypeId) {
491 $caseTypeName = CRM_Core_DAO
::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
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;
501 * Determine if modifications are allowed on the case-type
503 * @param int $caseTypeId
505 * TRUE if the definition can be modified
507 public static function isForkable($caseTypeId) {
508 $caseTypeName = CRM_Core_DAO
::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
510 // if file-based definition explicitly disables "forkable" option, then don't allow changes to definition
511 $fileDefinition = CRM_Case_XMLRepository
::singleton()->retrieveFile($caseTypeName);
512 if ($fileDefinition && isset($fileDefinition->forkable
)) {
513 return CRM_Utils_String
::strtobool((string) $fileDefinition->forkable
);