Merge pull request #15142 from eileenmcnaughton/pradeep2
[civicrm-core.git] / CRM / Case / BAO / CaseType.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2019
32 */
33
34 /**
35 * This class contains the functions for Case Type management.
36 */
37 class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
38
39 /**
40 * Static field for all the case information that we can potentially export.
41 *
42 * @var array
43 */
44 public static $_exportableFields = NULL;
45
46 /**
47 * Takes an associative array and creates a Case Type object.
48 *
49 * the function extract all the params it needs to initialize the create a
50 * case type object. the params array could contain additional unused name/value
51 * pairs
52 *
53 * @param array $params
54 * (reference ) an assoc array of name/value pairs.
55 *
56 * @throws CRM_Core_Exception
57 *
58 * @return CRM_Case_BAO_CaseType
59 */
60 public static function add(&$params) {
61 $caseTypeDAO = new CRM_Case_DAO_CaseType();
62
63 // form the name only if missing: CRM-627
64 $nameParam = CRM_Utils_Array::value('name', $params, NULL);
65 if (!$nameParam && empty($params['id'])) {
66 $params['name'] = CRM_Utils_String::titleToVar($params['title']);
67 }
68
69 // Old case-types (pre-4.5) may keep their ucky names, but new case-types must satisfy isValidName()
70 if (empty($params['id']) && !empty($params['name']) && !CRM_Case_BAO_CaseType::isValidName($params['name'])) {
71 throw new CRM_Core_Exception("Cannot create new case-type with malformed name [{$params['name']}]");
72 }
73
74 $caseTypeName = (isset($params['name'])) ? $params['name'] : CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $params['id'], 'name', 'id', TRUE);
75
76 // function to format definition column
77 if (isset($params['definition']) && is_array($params['definition'])) {
78 $params['definition'] = self::convertDefinitionToXML($caseTypeName, $params['definition']);
79 CRM_Core_ManagedEntities::scheduleReconciliation();
80 }
81
82 $caseTypeDAO->copyValues($params);
83 $result = $caseTypeDAO->save();
84 CRM_Case_XMLRepository::singleton()->flush();
85 return $result;
86 }
87
88 /**
89 * Generate and assign an arbitrary value to a field of a test object.
90 *
91 * @param string $fieldName
92 * @param array $fieldDef
93 * @param int $counter
94 * The globally-unique ID of the test object.
95 */
96 protected function assignTestValue($fieldName, &$fieldDef, $counter) {
97 if ($fieldName == 'definition') {
98 $this->{$fieldName} = "<CaseType><name>TestCaseType{$counter}</name></CaseType>";
99 }
100 else {
101 parent::assignTestValue($fieldName, $fieldDef, $counter);
102 }
103 }
104
105 /**
106 * Format / convert submitted array to xml for case type definition
107 *
108 * @param string $name
109 * @param array $definition
110 * The case-type definition expressed as an array-tree.
111 * @return string
112 * XML
113 */
114 public static function convertDefinitionToXML($name, $definition) {
115 $xmlFile = '<?xml version="1.0" encoding="utf-8" ?>' . "\n\n<CaseType>\n";
116 $xmlFile .= "<name>" . self::encodeXmlString($name) . "</name>\n";
117
118 if (array_key_exists('forkable', $definition)) {
119 $xmlFile .= "<forkable>" . ((int) $definition['forkable']) . "</forkable>\n";
120 }
121
122 if (isset($definition['activityTypes'])) {
123 $xmlFile .= "<ActivityTypes>\n";
124 foreach ($definition['activityTypes'] as $values) {
125 $xmlFile .= "<ActivityType>\n";
126 foreach ($values as $key => $value) {
127 $xmlFile .= "<{$key}>" . self::encodeXmlString($value) . "</{$key}>\n";
128 }
129 $xmlFile .= "</ActivityType>\n";
130 }
131 $xmlFile .= "</ActivityTypes>\n";
132 }
133
134 if (!empty($definition['statuses'])) {
135 $xmlFile .= "<Statuses>\n";
136 foreach ($definition['statuses'] as $value) {
137 $xmlFile .= "<Status>$value</Status>\n";
138 }
139 $xmlFile .= "</Statuses>\n";
140 }
141
142 if (isset($definition['activitySets'])) {
143 $xmlFile .= "<ActivitySets>\n";
144 foreach ($definition['activitySets'] as $k => $val) {
145 $xmlFile .= "<ActivitySet>\n";
146 foreach ($val as $index => $setVal) {
147 switch ($index) {
148 case 'activityTypes':
149 if (!empty($setVal)) {
150 $xmlFile .= "<ActivityTypes>\n";
151 foreach ($setVal as $values) {
152 $xmlFile .= "<ActivityType>\n";
153 foreach ($values as $key => $value) {
154 $xmlFile .= "<{$key}>" . self::encodeXmlString($value) . "</{$key}>\n";
155 }
156 $xmlFile .= "</ActivityType>\n";
157 }
158 $xmlFile .= "</ActivityTypes>\n";
159 }
160 break;
161
162 // passthrough
163 case 'sequence':
164 case 'timeline':
165 if ($setVal) {
166 $xmlFile .= "<{$index}>true</{$index}>\n";
167 }
168 break;
169
170 default:
171 $xmlFile .= "<{$index}>" . self::encodeXmlString($setVal) . "</{$index}>\n";
172 }
173 }
174
175 $xmlFile .= "</ActivitySet>\n";
176 }
177
178 $xmlFile .= "</ActivitySets>\n";
179 }
180
181 if (isset($definition['caseRoles'])) {
182 $xmlFile .= "<CaseRoles>\n";
183 foreach ($definition['caseRoles'] as $values) {
184 $xmlFile .= "<RelationshipType>\n";
185 foreach ($values as $key => $value) {
186 $xmlFile .= "<{$key}>" . self::encodeXmlString($value) . "</{$key}>\n";
187 }
188 $xmlFile .= "</RelationshipType>\n";
189 }
190 $xmlFile .= "</CaseRoles>\n";
191 }
192
193 if (array_key_exists('restrictActivityAsgmtToCmsUser', $definition)) {
194 $xmlFile .= "<RestrictActivityAsgmtToCmsUser>" . $definition['restrictActivityAsgmtToCmsUser'] . "</RestrictActivityAsgmtToCmsUser>\n";
195 }
196
197 if (!empty($definition['activityAsgmtGrps'])) {
198 $xmlFile .= "<ActivityAsgmtGrps>\n";
199 foreach ($definition['activityAsgmtGrps'] as $value) {
200 $xmlFile .= "<Group>$value</Group>\n";
201 }
202 $xmlFile .= "</ActivityAsgmtGrps>\n";
203 }
204
205 $xmlFile .= '</CaseType>';
206
207 return $xmlFile;
208 }
209
210 /**
211 * Ugh. This shouldn't exist. Use a real XML-encoder.
212 *
213 * Escape a string for use in XML.
214 *
215 * @param string $str
216 * A string which should outputted to XML.
217 * @return string
218 * @deprecated
219 */
220 protected static function encodeXmlString($str) {
221 // PHP 5.4: return htmlspecialchars($str, ENT_XML1, 'UTF-8')
222 return htmlspecialchars($str);
223 }
224
225 /**
226 * Get the case definition either from db or read from xml file.
227 *
228 * @param SimpleXmlElement $xml
229 * A single case-type record.
230 *
231 * @return array
232 * the definition of the case-type, expressed as PHP array-tree
233 */
234 public static function convertXmlToDefinition($xml) {
235 // build PHP array based on definition
236 $definition = [];
237
238 if (isset($xml->forkable)) {
239 $definition['forkable'] = (int) $xml->forkable;
240 }
241
242 if (isset($xml->RestrictActivityAsgmtToCmsUser)) {
243 $definition['restrictActivityAsgmtToCmsUser'] = (int) $xml->RestrictActivityAsgmtToCmsUser;
244 }
245
246 if (isset($xml->ActivityAsgmtGrps)) {
247 $definition['activityAsgmtGrps'] = (array) $xml->ActivityAsgmtGrps->Group;
248 }
249
250 // set activity types
251 if (isset($xml->ActivityTypes)) {
252 $definition['activityTypes'] = [];
253 foreach ($xml->ActivityTypes->ActivityType as $activityTypeXML) {
254 $definition['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE);
255 }
256 }
257
258 // set statuses
259 if (isset($xml->Statuses)) {
260 $definition['statuses'] = (array) $xml->Statuses->Status;
261 }
262
263 // set activity sets
264 if (isset($xml->ActivitySets)) {
265 $definition['activitySets'] = [];
266 $definition['timelineActivityTypes'] = [];
267
268 foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) {
269 // parse basic properties
270 $activitySet = [];
271 $activitySet['name'] = (string) $activitySetXML->name;
272 $activitySet['label'] = (string) $activitySetXML->label;
273 if ('true' == (string) $activitySetXML->timeline) {
274 $activitySet['timeline'] = 1;
275 }
276 if ('true' == (string) $activitySetXML->sequence) {
277 $activitySet['sequence'] = 1;
278 }
279
280 if (isset($activitySetXML->ActivityTypes)) {
281 $activitySet['activityTypes'] = [];
282 foreach ($activitySetXML->ActivityTypes->ActivityType as $activityTypeXML) {
283 $activityType = json_decode(json_encode($activityTypeXML), TRUE);
284 $activitySet['activityTypes'][] = $activityType;
285 if ($activitySetXML->timeline) {
286 $definition['timelineActivityTypes'][] = $activityType;
287 }
288 }
289 }
290 $definition['activitySets'][] = $activitySet;
291 }
292 }
293
294 // set case roles
295 if (isset($xml->CaseRoles)) {
296 $definition['caseRoles'] = [];
297 foreach ($xml->CaseRoles->RelationshipType as $caseRoleXml) {
298 $definition['caseRoles'][] = json_decode(json_encode($caseRoleXml), TRUE);
299 }
300 }
301
302 return $definition;
303 }
304
305 /**
306 * Given the list of params in the params array, fetch the object
307 * and store the values in the values array
308 *
309 * @param array $params
310 * Input parameters to find object.
311 * @param array $values
312 * Output values of the object.
313 *
314 * @return CRM_Case_BAO_CaseType|null the found object or null
315 */
316 public static function &getValues(&$params, &$values) {
317 $caseType = new CRM_Case_BAO_CaseType();
318
319 $caseType->copyValues($params);
320
321 if ($caseType->find(TRUE)) {
322 CRM_Core_DAO::storeValues($caseType, $values);
323 return $caseType;
324 }
325 return NULL;
326 }
327
328 /**
329 * Takes an associative array and creates a case type object.
330 *
331 * @param array $params
332 * (reference ) an assoc array of name/value pairs.
333 *
334 * @return CRM_Case_BAO_CaseType
335 */
336 public static function &create(&$params) {
337 $transaction = new CRM_Core_Transaction();
338
339 if (!empty($params['id'])) {
340 CRM_Utils_Hook::pre('edit', 'CaseType', $params['id'], $params);
341 }
342 else {
343 CRM_Utils_Hook::pre('create', 'CaseType', NULL, $params);
344 }
345
346 $caseType = self::add($params);
347
348 if (is_a($caseType, 'CRM_Core_Error')) {
349 $transaction->rollback();
350 return $caseType;
351 }
352
353 if (!empty($params['id'])) {
354 CRM_Utils_Hook::post('edit', 'CaseType', $caseType->id, $case);
355 }
356 else {
357 CRM_Utils_Hook::post('create', 'CaseType', $caseType->id, $case);
358 }
359 $transaction->commit();
360 CRM_Case_XMLRepository::singleton(TRUE);
361 CRM_Core_OptionGroup::flushAll();
362
363 return $caseType;
364 }
365
366 /**
367 * Retrieve DB object based on input parameters.
368 *
369 * It also stores all the retrieved values in the default array.
370 *
371 * @param array $params
372 * (reference ) an assoc array of name/value pairs.
373 * @param array $defaults
374 * (reference ) an assoc array to hold the name / value pairs.
375 * in a hierarchical manner
376 *
377 * @return CRM_Case_BAO_CaseType
378 */
379 public static function retrieve(&$params, &$defaults) {
380 $caseType = CRM_Case_BAO_CaseType::getValues($params, $defaults);
381 return $caseType;
382 }
383
384 /**
385 * @param int $caseTypeId
386 *
387 * @throws CRM_Core_Exception
388 * @return mixed
389 */
390 public static function del($caseTypeId) {
391 $caseType = new CRM_Case_DAO_CaseType();
392 $caseType->id = $caseTypeId;
393 $refCounts = $caseType->getReferenceCounts();
394 $total = array_sum(CRM_Utils_Array::collect('count', $refCounts));
395 if ($total) {
396 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]));
397 }
398 $result = $caseType->delete();
399 CRM_Case_XMLRepository::singleton(TRUE);
400 return $result;
401 }
402
403 /**
404 * Determine if a case-type name is well-formed
405 *
406 * @param string $caseType
407 * @return bool
408 */
409 public static function isValidName($caseType) {
410 return preg_match('/^[a-zA-Z0-9_]+$/', $caseType);
411 }
412
413 /**
414 * Determine if the case-type has *both* DB and file-based definitions.
415 *
416 * @param int $caseTypeId
417 * @return bool|null
418 * TRUE if there are *both* DB and file-based definitions
419 */
420 public static function isForked($caseTypeId) {
421 $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
422 if ($caseTypeName) {
423 $dbDefinition = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'definition', 'id', TRUE);
424 $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
425 return $fileDefinition && $dbDefinition;
426 }
427 return NULL;
428 }
429
430 /**
431 * Determine if modifications are allowed on the case-type
432 *
433 * @param int $caseTypeId
434 * @return bool
435 * TRUE if the definition can be modified
436 */
437 public static function isForkable($caseTypeId) {
438 $caseTypeName = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseTypeId, 'name', 'id', TRUE);
439 if ($caseTypeName) {
440 // if file-based definition explicitly disables "forkable" option, then don't allow changes to definition
441 $fileDefinition = CRM_Case_XMLRepository::singleton()->retrieveFile($caseTypeName);
442 if ($fileDefinition && isset($fileDefinition->forkable)) {
443 return CRM_Utils_String::strtobool((string) $fileDefinition->forkable);
444 }
445 }
446 return TRUE;
447 }
448
449 }