Merge pull request #17588 from artfulrobot/artfulrobot-property-bag-support-empty
[civicrm-core.git] / CRM / Case / XMLRepository.php
CommitLineData
fa1c763d
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
fa1c763d 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 |
fa1c763d 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
fa1c763d
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
fa1c763d
TO
16 *
17 * The XMLRepository is responsible for loading XML for case-types.
7458ddd3
TO
18 * It includes any bulk operations that apply across the list of all XML
19 * documents of all case-types.
fa1c763d
TO
20 */
21class CRM_Case_XMLRepository {
22 private static $singleton;
23
24 /**
51dda21e
SL
25 * @var array
26 * <String,SimpleXMLElement>
fa1c763d 27 */
be2fb01f 28 protected $xml = [];
fa1c763d
TO
29
30 /**
cc101011 31 * @var array|null
fa1c763d
TO
32 */
33 protected $hookCache = NULL;
34
35 /**
cc101011 36 * Symbolic names of case-types.
37 *
38 * @var array|null
fa1c763d
TO
39 */
40 protected $allCaseTypes = NULL;
41
42 /**
43 * @param bool $fresh
e19323c9 44 * @return CRM_Case_XMLRepository
fa1c763d
TO
45 */
46 public static function singleton($fresh = FALSE) {
47 if (!self::$singleton || $fresh) {
48 self::$singleton = new static();
49 }
50 return self::$singleton;
51 }
52
e492e278 53 public function flush() {
be2fb01f 54 $this->xml = [];
e492e278
TO
55 $this->hookCache = NULL;
56 $this->allCaseTypes = NULL;
be2fb01f 57 CRM_Core_DAO::$_dbColumnValueCache = [];
e492e278
TO
58 }
59
fa1c763d 60 /**
54957108 61 * Class constructor.
62 *
63 * @param array $allCaseTypes
64 * @param array $xml
fa1c763d 65 */
be2fb01f 66 public function __construct($allCaseTypes = NULL, $xml = []) {
e19323c9 67 $this->allCaseTypes = $allCaseTypes;
fa1c763d
TO
68 $this->xml = $xml;
69 }
70
71 /**
54957108 72 * Retrieve case.
73 *
fa1c763d 74 * @param string $caseType
54957108 75 *
76 * @return FALSE|\SimpleXMLElement
77 * @throws \CRM_Core_Exception
fa1c763d
TO
78 */
79 public function retrieve($caseType) {
ff7838ab 80 // check if xml definition is defined in db
81 $definition = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $caseType, 'definition', 'name');
82
83 if (!empty($definition)) {
edcc7f99
TO
84 list ($xml, $error) = CRM_Utils_XML::parseString($definition);
85 if (!$xml) {
86 throw new CRM_Core_Exception("Failed to parse CaseType XML: $error");
87 }
88 return $xml;
ff7838ab 89 }
90
46ec593d
TO
91 // TODO In 4.6 or 5.0, remove support for weird machine-names
92 //if (!CRM_Case_BAO_CaseType::isValidName($caseType)) {
93 // // perhaps caller provider a the label instead of the name?
94 // throw new CRM_Core_Exception("Cannot load caseType with malformed name [$caseType]");
95 //}
fa1c763d 96
f3acfdd9 97 if (empty($this->xml[$caseType])) {
32f1c917
TO
98 $fileXml = $this->retrieveFile($caseType);
99 if ($fileXml) {
100 $this->xml[$caseType] = $fileXml;
0db6c3e1
TO
101 }
102 else {
32f1c917 103 return FALSE;
46ec593d 104 }
32f1c917
TO
105 }
106 return $this->xml[$caseType];
107 }
bb68492c 108
32f1c917 109 /**
54957108 110 * Retrieve file.
111 *
32f1c917
TO
112 * @param string $caseType
113 * @return SimpleXMLElement|FALSE
114 */
115 public function retrieveFile($caseType) {
116 $fileName = NULL;
117 $fileXml = NULL;
fa1c763d 118
32f1c917
TO
119 if (CRM_Case_BAO_CaseType::isValidName($caseType)) {
120 // Search for a file based directly on the $caseType name
121 $fileName = $this->findXmlFile($caseType);
122 }
fa1c763d 123
32f1c917
TO
124 // For backward compatibility, also search for double-munged file names
125 // TODO In 4.6 or 5.0, remove support for loading double-munged file names
126 if (!$fileName || !file_exists($fileName)) {
127 $fileName = $this->findXmlFile(CRM_Case_XMLProcessor::mungeCaseType($caseType));
128 }
129
130 if ($fileName && file_exists($fileName)) {
fa1c763d
TO
131 // read xml file
132 $dom = new DomDocument();
f9857c59
JP
133 $xmlString = file_get_contents($fileName);
134 $dom->loadXML($xmlString);
135 $dom->documentURI = $fileName;
fa1c763d 136 $dom->xinclude();
32f1c917 137 $fileXml = simplexml_import_dom($dom);
fa1c763d 138 }
32f1c917
TO
139
140 return $fileXml;
fa1c763d
TO
141 }
142
a8354614 143 /**
54957108 144 * Find xml file.
145 *
a8354614 146 * @param string $caseType
72b3a70c
CW
147 * @return null|string
148 * file path
a8354614 149 */
e547f744
TO
150 public function findXmlFile($caseType) {
151 // first check custom templates directory
a8354614
TO
152 $fileName = NULL;
153
154 if (!$fileName || !file_exists($fileName)) {
155 $caseTypesViaHook = $this->getCaseTypesViaHook();
156 if (isset($caseTypesViaHook[$caseType], $caseTypesViaHook[$caseType]['file'])) {
157 $fileName = $caseTypesViaHook[$caseType]['file'];
158 }
159 }
160
161 if (!$fileName || !file_exists($fileName)) {
162 $config = CRM_Core_Config::singleton();
163 if (isset($config->customTemplateDir) && $config->customTemplateDir) {
164 // check if the file exists in the custom templates directory
165 $fileName = implode(DIRECTORY_SEPARATOR,
be2fb01f 166 [
a8354614
TO
167 $config->customTemplateDir,
168 'CRM',
169 'Case',
170 'xml',
171 'configuration',
172 "$caseType.xml",
be2fb01f 173 ]
a8354614
TO
174 );
175 }
176 }
177
178 if (!$fileName || !file_exists($fileName)) {
179 if (!file_exists($fileName)) {
180 // check if file exists locally
181 $fileName = implode(DIRECTORY_SEPARATOR,
be2fb01f 182 [
a8354614
TO
183 dirname(__FILE__),
184 'xml',
185 'configuration',
186 "$caseType.xml",
be2fb01f 187 ]
a8354614
TO
188 );
189 }
190
191 if (!file_exists($fileName)) {
192 // check if file exists locally
193 $fileName = implode(DIRECTORY_SEPARATOR,
be2fb01f 194 [
a8354614
TO
195 dirname(__FILE__),
196 'xml',
197 'configuration.sample',
198 "$caseType.xml",
be2fb01f 199 ]
a8354614
TO
200 );
201 }
202 }
203 return file_exists($fileName) ? $fileName : NULL;
204 }
205
fa1c763d
TO
206 /**
207 * @return array
208 * @see CRM_Utils_Hook::caseTypes
209 */
210 public function getCaseTypesViaHook() {
211 if ($this->hookCache === NULL) {
be2fb01f 212 $this->hookCache = [];
fa1c763d
TO
213 CRM_Utils_Hook::caseTypes($this->hookCache);
214 }
215 return $this->hookCache;
216 }
e19323c9
TO
217
218 /**
bb68492c 219 * @return array<string> symbolic names of case-types
e19323c9
TO
220 */
221 public function getAllCaseTypes() {
222 if ($this->allCaseTypes === NULL) {
223 $this->allCaseTypes = CRM_Case_PseudoConstant::caseType("name");
224 }
225 return $this->allCaseTypes;
226 }
227
7458ddd3
TO
228 /**
229 * @return array<string> symbolic-names of activity-types
230 */
231 public function getAllDeclaredActivityTypes() {
be2fb01f 232 $result = [];
7458ddd3
TO
233
234 $p = new CRM_Case_XMLProcessor_Process();
235 foreach ($this->getAllCaseTypes() as $caseTypeName) {
236 $caseTypeXML = $this->retrieve($caseTypeName);
237 $result = array_merge($result, $p->getDeclaredActivityTypes($caseTypeXML));
238 }
239
240 $result = array_unique($result);
241 sort($result);
242 return $result;
243 }
244
245 /**
d0a94888
AF
246 * Relationships are straight from XML, described from perspective of non-client
247 *
7458ddd3
TO
248 * @return array<string> symbolic-names of relationship-types
249 */
250 public function getAllDeclaredRelationshipTypes() {
be2fb01f 251 $result = [];
7458ddd3
TO
252
253 $p = new CRM_Case_XMLProcessor_Process();
254 foreach ($this->getAllCaseTypes() as $caseTypeName) {
255 $caseTypeXML = $this->retrieve($caseTypeName);
256 $result = array_merge($result, $p->getDeclaredRelationshipTypes($caseTypeXML));
257 }
258
259 $result = array_unique($result);
260 sort($result);
261 return $result;
262 }
263
e19323c9
TO
264 /**
265 * Determine the number of times a particular activity-type is
266 * referenced in CiviCase XML.
267 *
64bd5a0e
TO
268 * @param string $activityType
269 * Symbolic-name of an activity type.
e19323c9
TO
270 * @return int
271 */
00be9182 272 public function getActivityReferenceCount($activityType) {
e19323c9
TO
273 $p = new CRM_Case_XMLProcessor_Process();
274 $count = 0;
275 foreach ($this->getAllCaseTypes() as $caseTypeName) {
276 $caseTypeXML = $this->retrieve($caseTypeName);
277 if (in_array($activityType, $p->getDeclaredActivityTypes($caseTypeXML))) {
278 $count++;
279 }
280 }
281 return $count;
282 }
283
284 /**
285 * Determine the number of times a particular activity-type is
286 * referenced in CiviCase XML.
287 *
64bd5a0e
TO
288 * @param string $relationshipTypeName
289 * Symbolic-name of a relationship-type.
e19323c9
TO
290 * @return int
291 */
00be9182 292 public function getRelationshipReferenceCount($relationshipTypeName) {
e19323c9
TO
293 $p = new CRM_Case_XMLProcessor_Process();
294 $count = 0;
295 foreach ($this->getAllCaseTypes() as $caseTypeName) {
296 $caseTypeXML = $this->retrieve($caseTypeName);
297 if (in_array($relationshipTypeName, $p->getDeclaredRelationshipTypes($caseTypeXML))) {
298 $count++;
299 }
300 }
301 return $count;
302 }
303
fa1c763d 304}