Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 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 | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
13 | * | |
14 | * @package CRM | |
ca5cec67 | 15 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 | 16 | */ |
ee44263e | 17 | class CRM_Contact_BAO_ContactType extends CRM_Contact_DAO_ContactType implements \Civi\Core\HookInterface { |
6a488035 TO |
18 | |
19 | /** | |
4f940304 | 20 | * Retrieve DB object and copy to defaults array. |
6a488035 | 21 | * |
77c5b619 | 22 | * @param array $params |
4f940304 | 23 | * Array of criteria values. |
77c5b619 | 24 | * @param array $defaults |
4f940304 | 25 | * Array to be populated with found values. |
6a488035 | 26 | * |
4f940304 CW |
27 | * @return self|null |
28 | * The DAO object, if found. | |
29 | * | |
30 | * @deprecated | |
6a488035 | 31 | */ |
4f940304 CW |
32 | public static function retrieve($params, &$defaults) { |
33 | return self::commonRetrieve(self::class, $params, $defaults); | |
6a488035 TO |
34 | } |
35 | ||
86538308 | 36 | /** |
db01bf2f | 37 | * Is this contact type active. |
38 | * | |
39 | * @param string $contactType | |
86538308 EM |
40 | * |
41 | * @return bool | |
b5f3c53a | 42 | * |
43 | * @throws \API_Exception | |
86538308 | 44 | */ |
00be9182 | 45 | public static function isActive($contactType) { |
b5f3c53a | 46 | $contact = self::contactTypeInfo(); |
eb8dbd47 | 47 | return array_key_exists($contactType, $contact); |
6a488035 TO |
48 | } |
49 | ||
50 | /** | |
c490a46a | 51 | * Retrieve basic contact type information. |
6a488035 | 52 | * |
a18a4870 | 53 | * @param bool $includeInactive |
6a488035 | 54 | * |
a6c01b45 | 55 | * @return array |
16b10e64 | 56 | * Array of basic contact types information. |
a18a4870 | 57 | * |
58 | * @throws \API_Exception | |
59 | * @throws \Civi\API\Exception\UnauthorizedException | |
6a488035 | 60 | */ |
a18a4870 | 61 | public static function basicTypeInfo($includeInactive = FALSE) { |
8497e902 CW |
62 | return array_filter(self::getAllContactTypes(), function($type) use ($includeInactive) { |
63 | return empty($type['parent']) && ($includeInactive || $type['is_active']); | |
64 | }); | |
6a488035 TO |
65 | } |
66 | ||
67 | /** | |
c490a46a | 68 | * Retrieve all basic contact types. |
6a488035 | 69 | * |
da6b46f4 | 70 | * @param bool $all |
6a488035 | 71 | * |
a6c01b45 | 72 | * @return array |
16b10e64 | 73 | * Array of basic contact types |
7fc37770 | 74 | * |
75 | * @throws \API_Exception | |
76 | * @throws \Civi\API\Exception\UnauthorizedException | |
6a488035 | 77 | */ |
00be9182 | 78 | public static function basicTypes($all = FALSE) { |
6a488035 TO |
79 | return array_keys(self::basicTypeInfo($all)); |
80 | } | |
81 | ||
86538308 EM |
82 | /** |
83 | * @param bool $all | |
84 | * @param string $key | |
85 | * | |
86 | * @return array | |
7fc37770 | 87 | * @throws \API_Exception |
88 | * @throws \Civi\API\Exception\UnauthorizedException | |
86538308 | 89 | */ |
00be9182 | 90 | public static function basicTypePairs($all = FALSE, $key = 'name') { |
6a488035 TO |
91 | $subtypes = self::basicTypeInfo($all); |
92 | ||
be2fb01f | 93 | $pairs = []; |
6a488035 TO |
94 | foreach ($subtypes as $name => $info) { |
95 | $index = ($key == 'name') ? $name : $info[$key]; | |
96 | $pairs[$index] = $info['label']; | |
97 | } | |
98 | return $pairs; | |
99 | } | |
100 | ||
101 | /** | |
c490a46a | 102 | * Retrieve all subtypes Information. |
6a488035 | 103 | * |
8f004595 | 104 | * @param string|null $contactType |
fd31fa4c | 105 | * @param bool $all |
6a488035 | 106 | * |
a6c01b45 | 107 | * @return array |
b5f3c53a | 108 | * Array of sub type information, subset of getAllContactTypes. |
109 | * | |
110 | * @throws \API_Exception | |
6a488035 | 111 | */ |
b5f3c53a | 112 | public static function subTypeInfo($contactType = NULL, $all = FALSE) { |
113 | $contactTypes = self::getAllContactTypes(); | |
114 | foreach ($contactTypes as $index => $type) { | |
115 | if (empty($type['parent']) || | |
116 | (!$all && !$type['is_active']) | |
117 | || ($contactType && $type['parent'] !== $contactType) | |
118 | ) { | |
119 | unset($contactTypes[$index]); | |
81c4c148 | 120 | } |
6a488035 | 121 | } |
b5f3c53a | 122 | return $contactTypes; |
6a488035 TO |
123 | } |
124 | ||
125 | /** | |
126 | * | |
c490a46a | 127 | * retrieve all subtypes |
6a488035 | 128 | * |
8f004595 | 129 | * @param string|null $contactType |
f4aaa82a EM |
130 | * @param bool $all |
131 | * @param string $columnName | |
132 | * @param bool $ignoreCache | |
6a488035 | 133 | * |
a6c01b45 | 134 | * @return array |
16b10e64 CW |
135 | * all subtypes OR list of subtypes associated to |
136 | * a given basic contact type | |
b5f3c53a | 137 | * @throws \API_Exception |
6a488035 | 138 | */ |
00be9182 | 139 | public static function subTypes($contactType = NULL, $all = FALSE, $columnName = 'name', $ignoreCache = FALSE) { |
7fc37770 | 140 | if ($columnName === 'name') { |
8f004595 | 141 | return array_keys(self::subTypeInfo($contactType, $all)); |
6a488035 TO |
142 | } |
143 | else { | |
144 | return array_values(self::subTypePairs($contactType, FALSE, NULL, $ignoreCache)); | |
145 | } | |
146 | } | |
147 | ||
148 | /** | |
149 | * | |
c490a46a | 150 | * retrieve subtype pairs with name as 'subtype-name' and 'label' as value |
6a488035 | 151 | * |
77c5b619 | 152 | * @param array $contactType |
f4aaa82a | 153 | * @param bool $all |
8f004595 | 154 | * @param string|null $labelPrefix |
f4aaa82a | 155 | * @param bool $ignoreCache |
6a488035 | 156 | * |
72b3a70c CW |
157 | * @return array |
158 | * list of subtypes with name as 'subtype-name' and 'label' as value | |
6a488035 | 159 | */ |
00be9182 | 160 | public static function subTypePairs($contactType = NULL, $all = FALSE, $labelPrefix = '- ', $ignoreCache = FALSE) { |
8f004595 | 161 | $subtypes = self::subTypeInfo($contactType, $all); |
6a488035 | 162 | |
be2fb01f | 163 | $pairs = []; |
6a488035 TO |
164 | foreach ($subtypes as $name => $info) { |
165 | $pairs[$name] = $labelPrefix . $info['label']; | |
166 | } | |
167 | return $pairs; | |
168 | } | |
169 | ||
170 | /** | |
8497e902 | 171 | * Retrieve list of all types i.e basic + subtypes. |
6a488035 | 172 | * |
f4aaa82a | 173 | * @param bool $all |
6a488035 | 174 | * |
a6c01b45 | 175 | * @return array |
16b10e64 | 176 | * Array of basic types + all subtypes. |
6a488035 | 177 | */ |
00be9182 | 178 | public static function contactTypes($all = FALSE) { |
6a488035 TO |
179 | return array_keys(self::contactTypeInfo($all)); |
180 | } | |
181 | ||
182 | /** | |
db01bf2f | 183 | * Retrieve info array about all types i.e basic + subtypes. |
6a488035 | 184 | * |
ccd901a1 | 185 | * @todo deprecate calling this with $all = TRUE in favour of getAllContactTypes |
186 | * & ideally add getActiveContactTypes & call that from this fully | |
187 | * deprecated function. | |
188 | * | |
f4aaa82a | 189 | * @param bool $all |
6a488035 | 190 | * |
a6c01b45 | 191 | * @return array |
16b10e64 | 192 | * Array of basic types + all subtypes. |
6a488035 | 193 | */ |
7fc37770 | 194 | public static function contactTypeInfo($all = FALSE) { |
ccd901a1 | 195 | $contactTypes = self::getAllContactTypes(); |
196 | if (!$all) { | |
197 | foreach ($contactTypes as $index => $value) { | |
198 | if (!$value['is_active']) { | |
199 | unset($contactTypes[$index]); | |
6a488035 | 200 | } |
6a488035 TO |
201 | } |
202 | } | |
ccd901a1 | 203 | return $contactTypes; |
6a488035 TO |
204 | } |
205 | ||
206 | /** | |
db01bf2f | 207 | * Retrieve basic type pairs with name as 'built-in name' and 'label' as value. |
6a488035 | 208 | * |
f4aaa82a | 209 | * @param bool $all |
3fd42bb5 BT |
210 | * @param array|string|null $typeName |
211 | * @param string|null $delimiter | |
6a488035 | 212 | * |
8497e902 | 213 | * @return array|string |
16b10e64 | 214 | * Array of basictypes with name as 'built-in name' and 'label' as value |
ccd901a1 | 215 | * @throws \API_Exception |
6a488035 | 216 | */ |
00be9182 | 217 | public static function contactTypePairs($all = FALSE, $typeName = NULL, $delimiter = NULL) { |
6a488035 TO |
218 | $types = self::contactTypeInfo($all); |
219 | ||
220 | if ($typeName && !is_array($typeName)) { | |
221 | $typeName = explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($typeName, CRM_Core_DAO::VALUE_SEPARATOR)); | |
222 | } | |
223 | ||
be2fb01f | 224 | $pairs = []; |
6a488035 TO |
225 | if ($typeName) { |
226 | foreach ($typeName as $type) { | |
227 | if (array_key_exists($type, $types)) { | |
228 | $pairs[$type] = $types[$type]['label']; | |
229 | } | |
230 | } | |
231 | } | |
232 | else { | |
233 | foreach ($types as $name => $info) { | |
234 | $pairs[$name] = $info['label']; | |
235 | } | |
236 | } | |
237 | ||
238 | return !$delimiter ? $pairs : implode($delimiter, $pairs); | |
239 | } | |
240 | ||
b500fbea | 241 | /** |
fe482240 | 242 | * Get a list of elements for select box. |
b500fbea EM |
243 | * Note that this used to default to using the hex(01) character - which results in an invalid character being used in form fields |
244 | * which was not handled well be anything that loaded & resaved the html (outside core) | |
245 | * The use of this separator is now explicit in the calling functions as a step towards it's removal | |
246 | * | |
247 | * @param bool $all | |
248 | * @param bool $isSeparator | |
249 | * @param string $separator | |
250 | * | |
251 | * @return mixed | |
252 | */ | |
bed98343 | 253 | public static function getSelectElements( |
51ccfbbe | 254 | $all = FALSE, |
b500fbea EM |
255 | $isSeparator = TRUE, |
256 | $separator = '__' | |
6a488035 | 257 | ) { |
81c4c148 | 258 | // @todo - use Cache class - ie like Civi::cache('contactTypes') |
6a488035 TO |
259 | static $_cache = NULL; |
260 | ||
261 | if ($_cache === NULL) { | |
be2fb01f | 262 | $_cache = []; |
6a488035 TO |
263 | } |
264 | ||
ccd901a1 | 265 | // @todo - call getAllContactTypes & return filtered results. |
6a488035 | 266 | $argString = $all ? 'CRM_CT_GSE_1' : 'CRM_CT_GSE_0'; |
b500fbea | 267 | $argString .= $isSeparator ? '_1' : '_0'; |
e9567ca3 | 268 | $argString .= $separator; |
77f080cb | 269 | $argString = CRM_Utils_Cache::cleanKey($argString); |
6a488035 TO |
270 | if (!array_key_exists($argString, $_cache)) { |
271 | $cache = CRM_Utils_Cache::singleton(); | |
272 | $_cache[$argString] = $cache->get($argString); | |
273 | ||
274 | if (!$_cache[$argString]) { | |
be2fb01f | 275 | $_cache[$argString] = []; |
6a488035 | 276 | |
7fc37770 | 277 | $sql = ' |
6a488035 TO |
278 | SELECT c.name as child_name , c.label as child_label , c.id as child_id, |
279 | p.name as parent_name, p.label as parent_label, p.id as parent_id | |
280 | FROM civicrm_contact_type c | |
281 | LEFT JOIN civicrm_contact_type p ON ( c.parent_id = p.id ) | |
282 | WHERE ( c.name IS NOT NULL ) | |
7fc37770 | 283 | '; |
6a488035 TO |
284 | |
285 | if ($all === FALSE) { | |
7fc37770 | 286 | $sql .= ' |
6a488035 TO |
287 | AND c.is_active = 1 |
288 | AND ( p.is_active = 1 OR p.id IS NULL ) | |
7fc37770 | 289 | '; |
6a488035 TO |
290 | } |
291 | $sql .= " ORDER BY c.id"; | |
292 | ||
be2fb01f | 293 | $values = []; |
6a488035 TO |
294 | $dao = CRM_Core_DAO::executeQuery($sql); |
295 | while ($dao->fetch()) { | |
296 | if (!empty($dao->parent_id)) { | |
353ffa53 | 297 | $key = $isSeparator ? $dao->parent_name . $separator . $dao->child_name : $dao->child_name; |
bee6039a | 298 | $label = "- {$dao->child_label}"; |
6a488035 TO |
299 | $pName = $dao->parent_name; |
300 | } | |
301 | else { | |
353ffa53 | 302 | $key = $dao->child_name; |
6a488035 TO |
303 | $label = $dao->child_label; |
304 | $pName = $dao->child_name; | |
305 | } | |
306 | ||
307 | if (!isset($values[$pName])) { | |
be2fb01f | 308 | $values[$pName] = []; |
6a488035 | 309 | } |
be2fb01f | 310 | $values[$pName][] = ['key' => $key, 'label' => $label]; |
6a488035 TO |
311 | } |
312 | ||
be2fb01f | 313 | $selectElements = []; |
6a488035 TO |
314 | foreach ($values as $pName => $elements) { |
315 | foreach ($elements as $element) { | |
316 | $selectElements[$element['key']] = $element['label']; | |
317 | } | |
318 | } | |
319 | $_cache[$argString] = $selectElements; | |
320 | ||
321 | $cache->set($argString, $_cache[$argString]); | |
322 | } | |
323 | } | |
324 | return $_cache[$argString]; | |
325 | } | |
326 | ||
327 | /** | |
fe482240 | 328 | * Check if a given type is a subtype. |
6a488035 | 329 | * |
77c5b619 TO |
330 | * @param string $subType |
331 | * Contact subType. | |
f4aaa82a | 332 | * @param bool $ignoreCache |
6a488035 | 333 | * |
bed98343 | 334 | * @return bool |
a6c01b45 | 335 | * true if subType, false otherwise. |
6a488035 | 336 | */ |
00be9182 | 337 | public static function isaSubType($subType, $ignoreCache = FALSE) { |
6a488035 TO |
338 | return in_array($subType, self::subTypes(NULL, TRUE, 'name', $ignoreCache)); |
339 | } | |
340 | ||
341 | /** | |
100fef9d | 342 | * Retrieve the basic contact type associated with given subType. |
6a488035 | 343 | * |
683bf891 SL |
344 | * @param array|string $subType contact subType. |
345 | * @return array|string | |
6a488035 | 346 | */ |
00be9182 | 347 | public static function getBasicType($subType) { |
81c4c148 | 348 | // @todo - use Cache class - ie like Civi::cache('contactTypes') |
6a488035 TO |
349 | static $_cache = NULL; |
350 | if ($_cache === NULL) { | |
be2fb01f | 351 | $_cache = []; |
6a488035 TO |
352 | } |
353 | ||
354 | $isArray = TRUE; | |
355 | if ($subType && !is_array($subType)) { | |
be2fb01f | 356 | $subType = [$subType]; |
6a488035 TO |
357 | $isArray = FALSE; |
358 | } | |
359 | $argString = implode('_', $subType); | |
360 | ||
361 | if (!array_key_exists($argString, $_cache)) { | |
be2fb01f | 362 | $_cache[$argString] = []; |
6a488035 TO |
363 | |
364 | $sql = " | |
365 | SELECT subtype.name as contact_subtype, type.name as contact_type | |
366 | FROM civicrm_contact_type subtype | |
367 | INNER JOIN civicrm_contact_type type ON ( subtype.parent_id = type.id ) | |
368 | WHERE subtype.name IN ('" . implode("','", $subType) . "' )"; | |
369 | $dao = CRM_Core_DAO::executeQuery($sql); | |
370 | while ($dao->fetch()) { | |
371 | if (!$isArray) { | |
372 | $_cache[$argString] = $dao->contact_type; | |
373 | break; | |
374 | } | |
375 | $_cache[$argString][$dao->contact_subtype] = $dao->contact_type; | |
376 | } | |
377 | } | |
378 | return $_cache[$argString]; | |
379 | } | |
380 | ||
381 | /** | |
c490a46a | 382 | * Suppress all subtypes present in given array. |
6a488035 | 383 | * |
77c5b619 TO |
384 | * @param array $subTypes |
385 | * Contact subTypes. | |
f4aaa82a | 386 | * @param bool $ignoreCache |
6a488035 | 387 | * |
a6c01b45 | 388 | * @return array |
16b10e64 | 389 | * Array of suppressed subTypes. |
6a488035 | 390 | */ |
00be9182 | 391 | public static function suppressSubTypes(&$subTypes, $ignoreCache = FALSE) { |
6a488035 TO |
392 | $subTypes = array_diff($subTypes, self::subTypes(NULL, TRUE, 'name', $ignoreCache)); |
393 | return $subTypes; | |
394 | } | |
395 | ||
396 | /** | |
100fef9d | 397 | * Verify if a given subtype is associated with a given basic contact type. |
6a488035 | 398 | * |
77c5b619 TO |
399 | * @param string $subType |
400 | * Contact subType. | |
401 | * @param string $contactType | |
402 | * Contact Type. | |
f4aaa82a EM |
403 | * @param bool $ignoreCache |
404 | * @param string $columnName | |
6a488035 | 405 | * |
bed98343 | 406 | * @return bool |
a6c01b45 | 407 | * true if contact extends, false otherwise. |
6a488035 | 408 | */ |
00be9182 | 409 | public static function isExtendsContactType($subType, $contactType, $ignoreCache = FALSE, $columnName = 'name') { |
1b23469d | 410 | $subType = (array) CRM_Utils_Array::explodePadded($subType); |
6a488035 TO |
411 | $subtypeList = self::subTypes($contactType, TRUE, $columnName, $ignoreCache); |
412 | $intersection = array_intersect($subType, $subtypeList); | |
413 | return $subType == $intersection; | |
414 | } | |
415 | ||
416 | /** | |
fe482240 | 417 | * Create shortcuts menu for contactTypes. |
6a488035 | 418 | * |
a6c01b45 CW |
419 | * @return array |
420 | * of contactTypes | |
6a488035 | 421 | */ |
00be9182 | 422 | public static function getCreateNewList() { |
be2fb01f | 423 | $shortCuts = []; |
b500fbea EM |
424 | //@todo FIXME - using the CRM_Core_DAO::VALUE_SEPARATOR creates invalid html - if you can find the form |
425 | // this is loaded onto then replace with something like '__' & test | |
426 | $separator = CRM_Core_DAO::VALUE_SEPARATOR; | |
427 | $contactTypes = self::getSelectElements(FALSE, TRUE, $separator); | |
6a488035 TO |
428 | foreach ($contactTypes as $key => $value) { |
429 | if ($key) { | |
430 | $typeValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $key); | |
9c1bc317 | 431 | $cType = $typeValue['0'] ?? NULL; |
7926b999 | 432 | $typeUrl = 'ct=' . $cType; |
6a488035 TO |
433 | if ($csType = CRM_Utils_Array::value('1', $typeValue)) { |
434 | $typeUrl .= "&cst=$csType"; | |
435 | } | |
be2fb01f | 436 | $shortCut = [ |
6a488035 TO |
437 | 'path' => 'civicrm/contact/add', |
438 | 'query' => "$typeUrl&reset=1", | |
439 | 'ref' => "new-$value", | |
440 | 'title' => $value, | |
be2fb01f | 441 | ]; |
7926b999 | 442 | if ($csType = CRM_Utils_Array::value('1', $typeValue)) { |
443 | $shortCuts[$cType]['shortCuts'][] = $shortCut; | |
444 | } | |
445 | else { | |
446 | $shortCuts[$cType] = $shortCut; | |
447 | } | |
6a488035 TO |
448 | } |
449 | } | |
450 | return $shortCuts; | |
451 | } | |
452 | ||
453 | /** | |
fe482240 | 454 | * Delete Contact SubTypes. |
6a488035 | 455 | * |
77c5b619 TO |
456 | * @param int $contactTypeId |
457 | * ID of the Contact Subtype to be deleted. | |
ace511f2 | 458 | * @deprecated |
f4aaa82a | 459 | * @return bool |
6a488035 | 460 | */ |
00be9182 | 461 | public static function del($contactTypeId) { |
6a488035 TO |
462 | if (!$contactTypeId) { |
463 | return FALSE; | |
464 | } | |
ace511f2 CW |
465 | try { |
466 | static::deleteRecord(['id' => $contactTypeId]); | |
467 | return TRUE; | |
468 | } | |
469 | catch (CRM_Core_Exception $e) { | |
6a488035 TO |
470 | return FALSE; |
471 | } | |
ace511f2 CW |
472 | } |
473 | ||
474 | /** | |
475 | * Callback for hook_civicrm_pre(). | |
476 | * @param \Civi\Core\Event\PreEvent $event | |
477 | * @throws CRM_Core_Exception | |
478 | */ | |
479 | public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) { | |
480 | // Before deleting a contactType, check references by custom groups | |
481 | if ($event->action === 'delete') { | |
482 | $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_ContactType', $event->id); | |
483 | $sep = CRM_Core_DAO::VALUE_SEPARATOR; | |
484 | $custom = new CRM_Core_DAO_CustomGroup(); | |
485 | $custom->whereAdd("extends_entity_column_value LIKE '%{$sep}{$name}{$sep}%'"); | |
486 | if ($custom->find()) { | |
487 | throw new CRM_Core_Exception(ts("You can not delete this contact type -- it is used by %1 custom field group(s). The custom fields must be deleted first.", [1 => $custom->N])); | |
488 | } | |
489 | } | |
490 | } | |
6a488035 | 491 | |
ace511f2 CW |
492 | /** |
493 | * Callback for hook_civicrm_post(). | |
494 | * @param \Civi\Core\Event\PostEvent $event | |
495 | */ | |
496 | public static function self_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) { | |
497 | if ($event->action === 'delete') { | |
498 | $sep = CRM_Core_DAO::VALUE_SEPARATOR; | |
499 | $subType = "$sep{$event->object->name}$sep"; | |
500 | // For contacts with just the one sub-type, set to null | |
501 | $sql = " | |
6a488035 | 502 | UPDATE civicrm_contact SET contact_sub_type = NULL |
ace511f2 CW |
503 | WHERE contact_sub_type = '$subType'"; |
504 | CRM_Core_DAO::executeQuery($sql); | |
505 | // For contacts with multipe sub-types, remove this one | |
506 | $sql = " | |
507 | UPDATE civicrm_contact SET contact_sub_type = REPLACE(contact_sub_type, '$subType', '$sep') | |
508 | WHERE contact_sub_type LIKE '%{$subType}%'"; | |
509 | CRM_Core_DAO::executeQuery($sql); | |
510 | ||
511 | // remove navigation entry which was auto-created when this sub-type was added | |
512 | \Civi\Api4\Navigation::delete(FALSE) | |
513 | ->addWhere('name', '=', "New {$event->object->name}") | |
514 | ->addWhere('url', 'LIKE', 'civicrm/contact/add%') | |
515 | // Overide the default which limits to a single domain | |
516 | ->addWhere('domain_id', '>', 0) | |
517 | ->execute(); | |
6a488035 | 518 | |
81c4c148 | 519 | Civi::cache('contactTypes')->clear(); |
6a488035 | 520 | } |
6a488035 TO |
521 | } |
522 | ||
523 | /** | |
fe482240 | 524 | * Add or update Contact SubTypes. |
6a488035 | 525 | * |
77c5b619 TO |
526 | * @param array $params |
527 | * An assoc array of name/value pairs. | |
6a488035 | 528 | * |
bed98343 | 529 | * @return object|void |
7fc37770 | 530 | * @throws \CRM_Core_Exception |
6a488035 | 531 | */ |
eb8dbd47 | 532 | public static function add($params) { |
6a488035 TO |
533 | |
534 | // label or name | |
0f77a625 | 535 | if (empty($params['id']) && empty($params['label'])) { |
351e8d47 | 536 | // @todo consider throwing exception instead. |
bed98343 | 537 | return NULL; |
6a488035 | 538 | } |
351e8d47 | 539 | if (empty($params['id']) && empty($params['name'])) { |
540 | $params['name'] = ucfirst(CRM_Utils_String::munge($params['label'])); | |
541 | } | |
a7488080 | 542 | if (!empty($params['parent_id']) && |
6a488035 TO |
543 | !CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_ContactType', $params['parent_id']) |
544 | ) { | |
bed98343 | 545 | return NULL; |
6a488035 TO |
546 | } |
547 | ||
548 | $contactType = new CRM_Contact_DAO_ContactType(); | |
549 | $contactType->copyValues($params); | |
9c1bc317 | 550 | $contactType->id = $params['id'] ?? NULL; |
6a488035 | 551 | |
6a488035 TO |
552 | $contactType->save(); |
553 | if ($contactType->find(TRUE)) { | |
554 | $contactName = $contactType->name; | |
353ffa53 TO |
555 | $contact = ucfirst($contactType->label); |
556 | $active = $contactType->is_active; | |
6a488035 TO |
557 | } |
558 | ||
a7488080 | 559 | if (!empty($params['id'])) { |
be2fb01f | 560 | $newParams = [ |
de44b462 | 561 | 'label' => ts("New %1", [1 => $contact]), |
351e8d47 | 562 | 'is_active' => $contactType->is_active, |
be2fb01f | 563 | ]; |
1237d8d7 | 564 | CRM_Core_BAO_Navigation::processUpdate(['name' => "New $contactName"], $newParams); |
6a488035 TO |
565 | } |
566 | else { | |
567 | $name = self::getBasicType($contactName); | |
568 | if (!$name) { | |
683bf891 | 569 | return NULL; |
6a488035 | 570 | } |
be2fb01f | 571 | $value = ['name' => "New $name"]; |
6a488035 | 572 | CRM_Core_BAO_Navigation::retrieve($value, $navinfo); |
be2fb01f | 573 | $navigation = [ |
de44b462 | 574 | 'label' => ts("New %1", [1 => $contact]), |
6a488035 | 575 | 'name' => "New $contactName", |
15a7ea79 | 576 | 'url' => "civicrm/contact/add?ct=$name&cst=$contactName&reset=1", |
6a488035 TO |
577 | 'permission' => 'add contacts', |
578 | 'parent_id' => $navinfo['id'], | |
579 | 'is_active' => $active, | |
be2fb01f | 580 | ]; |
6a488035 TO |
581 | CRM_Core_BAO_Navigation::add($navigation); |
582 | } | |
583 | CRM_Core_BAO_Navigation::resetNavigation(); | |
81c4c148 | 584 | Civi::cache('contactTypes')->clear(); |
6a488035 TO |
585 | |
586 | return $contactType; | |
587 | } | |
588 | ||
589 | /** | |
fe482240 | 590 | * Update the is_active flag in the db. |
6a488035 | 591 | * |
77c5b619 TO |
592 | * @param int $id |
593 | * Id of the database record. | |
594 | * @param bool $is_active | |
595 | * Value we want to set the is_active field. | |
6a488035 | 596 | * |
8a4fede3 | 597 | * @return bool |
598 | * true if we found and updated the object, else false | |
6a488035 | 599 | */ |
00be9182 | 600 | public static function setIsActive($id, $is_active) { |
be2fb01f | 601 | $params = ['id' => $id]; |
6a488035 | 602 | self::retrieve($params, $contactinfo); |
be2fb01f CW |
603 | $params = ['name' => "New $contactinfo[name]"]; |
604 | $newParams = ['is_active' => $is_active]; | |
6a488035 TO |
605 | CRM_Core_BAO_Navigation::processUpdate($params, $newParams); |
606 | CRM_Core_BAO_Navigation::resetNavigation(); | |
607 | return CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_ContactType', $id, | |
608 | 'is_active', $is_active | |
609 | ); | |
610 | } | |
611 | ||
86538308 | 612 | /** |
100fef9d | 613 | * @param string $typeName |
86538308 | 614 | * |
ccd901a1 | 615 | * @return string |
616 | * @throws \API_Exception | |
86538308 | 617 | */ |
00be9182 | 618 | public static function getLabel($typeName) { |
6a488035 TO |
619 | $types = self::contactTypeInfo(TRUE); |
620 | ||
621 | if (array_key_exists($typeName, $types)) { | |
622 | return $types[$typeName]['label']; | |
623 | } | |
624 | return $typeName; | |
625 | } | |
626 | ||
627 | /** | |
100fef9d | 628 | * Check whether allow to change any contact's subtype |
6a488035 TO |
629 | * on the basis of custom data and relationship of specific subtype |
630 | * currently used in contact/edit form amd in import validation | |
631 | * | |
77c5b619 TO |
632 | * @param int $contactId |
633 | * Contact id. | |
634 | * @param string $subType | |
635 | * Subtype. | |
6a488035 | 636 | * |
bed98343 | 637 | * @return bool |
7fc37770 | 638 | * @throws \CRM_Core_Exception |
6a488035 | 639 | */ |
00be9182 | 640 | public static function isAllowEdit($contactId, $subType = NULL) { |
6a488035 TO |
641 | |
642 | if (!$contactId) { | |
643 | return TRUE; | |
644 | } | |
645 | ||
646 | if (empty($subType)) { | |
647 | $subType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', | |
648 | $contactId, | |
649 | 'contact_sub_type' | |
650 | ); | |
651 | } | |
652 | ||
653 | if (self::hasCustomData($subType, $contactId) || self::hasRelationships($contactId, $subType)) { | |
654 | return FALSE; | |
655 | } | |
656 | ||
657 | return TRUE; | |
658 | } | |
659 | ||
86538308 EM |
660 | /** |
661 | * @param $contactType | |
100fef9d | 662 | * @param int $contactId |
86538308 EM |
663 | * |
664 | * @return bool | |
665 | */ | |
00be9182 | 666 | public static function hasCustomData($contactType, $contactId = NULL) { |
6a488035 TO |
667 | $subTypeClause = ''; |
668 | ||
669 | if (self::isaSubType($contactType)) { | |
670 | $subType = $contactType; | |
671 | $contactType = self::getBasicType($subType); | |
672 | ||
673 | // check for empty custom data which extends subtype | |
674 | $subTypeValue = CRM_Core_DAO::VALUE_SEPARATOR . $subType . CRM_Core_DAO::VALUE_SEPARATOR; | |
675 | $subTypeClause = " AND extends_entity_column_value LIKE '%{$subTypeValue}%' "; | |
676 | } | |
677 | $query = "SELECT table_name FROM civicrm_custom_group WHERE extends = '{$contactType}' {$subTypeClause}"; | |
678 | ||
679 | $dao = CRM_Core_DAO::executeQuery($query); | |
680 | while ($dao->fetch()) { | |
681 | $sql = "SELECT count(id) FROM {$dao->table_name}"; | |
682 | if ($contactId) { | |
683 | $sql .= " WHERE entity_id = {$contactId}"; | |
684 | } | |
685 | $sql .= " LIMIT 1"; | |
686 | ||
687 | $customDataCount = CRM_Core_DAO::singleValueQuery($sql); | |
688 | if (!empty($customDataCount)) { | |
6a488035 TO |
689 | return TRUE; |
690 | } | |
691 | } | |
692 | return FALSE; | |
693 | } | |
694 | ||
f4aaa82a EM |
695 | /** |
696 | * @todo what does this function do? | |
100fef9d | 697 | * @param int $contactId |
f4aaa82a EM |
698 | * @param $contactType |
699 | * | |
700 | * @return bool | |
701 | */ | |
00be9182 | 702 | public static function hasRelationships($contactId, $contactType) { |
6a488035 | 703 | if (self::isaSubType($contactType)) { |
353ffa53 TO |
704 | $subType = $contactType; |
705 | $contactType = self::getBasicType($subType); | |
6a488035 TO |
706 | $subTypeClause = " AND ( ( crt.contact_type_a = '{$contactType}' AND crt.contact_sub_type_a = '{$subType}') OR |
707 | ( crt.contact_type_b = '{$contactType}' AND crt.contact_sub_type_b = '{$subType}') ) "; | |
708 | } | |
709 | else { | |
710 | $subTypeClause = " AND ( crt.contact_type_a = '{$contactType}' OR crt.contact_type_b = '{$contactType}' ) "; | |
711 | } | |
712 | ||
713 | // check relationships for | |
714 | $relationshipQuery = " | |
715 | SELECT count(cr.id) FROM civicrm_relationship cr | |
716 | INNER JOIN civicrm_relationship_type crt ON | |
717 | ( cr.relationship_type_id = crt.id {$subTypeClause} ) | |
718 | WHERE ( cr.contact_id_a = {$contactId} OR cr.contact_id_b = {$contactId} ) | |
719 | LIMIT 1"; | |
720 | ||
721 | $relationshipCount = CRM_Core_DAO::singleValueQuery($relationshipQuery); | |
722 | ||
723 | if (!empty($relationshipCount)) { | |
724 | return TRUE; | |
725 | } | |
726 | ||
727 | return FALSE; | |
728 | } | |
729 | ||
f4aaa82a | 730 | /** |
f4aaa82a EM |
731 | * @param $contactType |
732 | * @param array $subtypeSet | |
733 | * | |
734 | * @return array | |
7fc37770 | 735 | * @throws \CRM_Core_Exception |
736 | * @todo what does this function do? | |
f4aaa82a | 737 | */ |
be2fb01f | 738 | public static function getSubtypeCustomPair($contactType, $subtypeSet = []) { |
6a488035 TO |
739 | if (empty($subtypeSet)) { |
740 | return $subtypeSet; | |
741 | } | |
742 | ||
be2fb01f | 743 | $customSet = $subTypeClause = []; |
6a488035 | 744 | foreach ($subtypeSet as $subtype) { |
353ffa53 | 745 | $subtype = CRM_Utils_Type::escape($subtype, 'String'); |
8ed93e75 | 746 | $subtype = CRM_Core_DAO::VALUE_SEPARATOR . $subtype . CRM_Core_DAO::VALUE_SEPARATOR; |
6a488035 TO |
747 | $subTypeClause[] = "extends_entity_column_value LIKE '%{$subtype}%' "; |
748 | } | |
7fc37770 | 749 | $query = 'SELECT table_name |
6a488035 | 750 | FROM civicrm_custom_group |
7fc37770 | 751 | WHERE extends = %1 AND ' . implode(" OR ", $subTypeClause); |
be2fb01f | 752 | $dao = CRM_Core_DAO::executeQuery($query, [1 => [$contactType, 'String']]); |
6a488035 TO |
753 | while ($dao->fetch()) { |
754 | $customSet[] = $dao->table_name; | |
755 | } | |
756 | return array_unique($customSet); | |
757 | } | |
758 | ||
f4aaa82a | 759 | /** |
fe482240 | 760 | * Function that does something. |
f4aaa82a | 761 | * |
100fef9d | 762 | * @param int $contactID |
ccd901a1 | 763 | * @param string $contactType |
f4aaa82a EM |
764 | * @param array $oldSubtypeSet |
765 | * @param array $newSubtypeSet | |
766 | * | |
767 | * @return bool | |
ccd901a1 | 768 | * @throws \CRM_Core_Exception |
769 | * | |
770 | * @todo what does this function do? | |
f4aaa82a | 771 | */ |
bed98343 | 772 | public static function deleteCustomSetForSubtypeMigration( |
51ccfbbe | 773 | $contactID, |
6a488035 | 774 | $contactType, |
be2fb01f CW |
775 | $oldSubtypeSet = [], |
776 | $newSubtypeSet = [] | |
6a488035 TO |
777 | ) { |
778 | $oldCustomSet = self::getSubtypeCustomPair($contactType, $oldSubtypeSet); | |
779 | $newCustomSet = self::getSubtypeCustomPair($contactType, $newSubtypeSet); | |
780 | ||
781 | $customToBeRemoved = array_diff($oldCustomSet, $newCustomSet); | |
782 | foreach ($customToBeRemoved as $customTable) { | |
783 | self::deleteCustomRowsForEntityID($customTable, $contactID); | |
784 | } | |
785 | return TRUE; | |
786 | } | |
787 | ||
788 | /** | |
789 | * Delete content / rows of a custom table specific to a subtype for a given custom-group. | |
790 | * This function currently works for contact subtypes only and could be later improved / genralized | |
791 | * to work for other subtypes as well. | |
792 | * | |
77c5b619 TO |
793 | * @param int $gID |
794 | * Custom group id. | |
795 | * @param array $subtypes | |
796 | * List of subtypes related to which entry is to be removed. | |
683bf891 | 797 | * @param array $subtypesToPreserve |
6a488035 | 798 | * |
67d19299 | 799 | * @return bool |
ccd901a1 | 800 | * |
801 | * @throws \CRM_Core_Exception | |
6a488035 | 802 | */ |
be2fb01f | 803 | public static function deleteCustomRowsOfSubtype($gID, $subtypes = [], $subtypesToPreserve = []) { |
6a488035 TO |
804 | if (!$gID or empty($subtypes)) { |
805 | return FALSE; | |
806 | } | |
807 | ||
808 | $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $gID, 'table_name'); | |
809 | ||
66ed0bee | 810 | // drop triggers CRM-13587 |
811 | CRM_Core_DAO::dropTriggers($tableName); | |
3d5ac964 | 812 | |
3dfaea8d | 813 | foreach ($subtypesToPreserve as $subtypeToPreserve) { |
89f0019f | 814 | $subtypeToPreserve = CRM_Utils_Type::escape($subtypeToPreserve, 'String'); |
815 | $subtypesToPreserveClause[] = "(civicrm_contact.contact_sub_type NOT LIKE '%" . CRM_Core_DAO::VALUE_SEPARATOR . $subtypeToPreserve . CRM_Core_DAO::VALUE_SEPARATOR . "%')"; | |
3dfaea8d | 816 | } |
817 | $subtypesToPreserveClause = implode(' AND ', $subtypesToPreserveClause); | |
66ed0bee | 818 | |
be2fb01f | 819 | $subtypeClause = []; |
6a488035 TO |
820 | foreach ($subtypes as $subtype) { |
821 | $subtype = CRM_Utils_Type::escape($subtype, 'String'); | |
3dfaea8d | 822 | $subtypeClause[] = "( civicrm_contact.contact_sub_type LIKE '%" . CRM_Core_DAO::VALUE_SEPARATOR . $subtype . CRM_Core_DAO::VALUE_SEPARATOR . "%'" |
3d5ac964 | 823 | . " AND " . $subtypesToPreserveClause . ")"; |
6a488035 TO |
824 | } |
825 | $subtypeClause = implode(' OR ', $subtypeClause); | |
826 | ||
827 | $query = "DELETE custom.* | |
828 | FROM {$tableName} custom | |
829 | INNER JOIN civicrm_contact ON civicrm_contact.id = custom.entity_id | |
830 | WHERE ($subtypeClause)"; | |
66ed0bee | 831 | |
832 | CRM_Core_DAO::singleValueQuery($query); | |
833 | ||
834 | // rebuild triggers CRM-13587 | |
835 | CRM_Core_DAO::triggerRebuild($tableName); | |
6a488035 TO |
836 | } |
837 | ||
838 | /** | |
839 | * Delete content / rows of a custom table specific entity-id for a given custom-group table. | |
840 | * | |
77c5b619 TO |
841 | * @param int $customTable |
842 | * Custom table name. | |
843 | * @param int $entityID | |
844 | * Entity id. | |
6a488035 | 845 | * |
67d19299 | 846 | * @return null|string |
ccd901a1 | 847 | * |
848 | * @throws \CRM_Core_Exception | |
6a488035 | 849 | */ |
d9ef38cc | 850 | public static function deleteCustomRowsForEntityID($customTable, $entityID) { |
6a488035 TO |
851 | $customTable = CRM_Utils_Type::escape($customTable, 'String'); |
852 | $query = "DELETE FROM {$customTable} WHERE entity_id = %1"; | |
be2fb01f | 853 | return CRM_Core_DAO::singleValueQuery($query, [1 => [$entityID, 'Integer']]); |
6a488035 | 854 | } |
96025800 | 855 | |
ccd901a1 | 856 | /** |
857 | * Get all contact types, leveraging caching. | |
858 | * | |
8497e902 CW |
859 | * Note, this function is used within APIv4 Entity.get, so must use a |
860 | * SQL query instead of calling APIv4 to avoid an infinite loop. | |
ccd901a1 | 861 | * |
8497e902 | 862 | * @return array |
ccd901a1 | 863 | * @throws \API_Exception |
864 | */ | |
30238b5e | 865 | public static function getAllContactTypes() { |
3c33fb16 TO |
866 | $cache = Civi::cache('contactTypes'); |
867 | $cacheKey = 'all_' . $GLOBALS['tsLocale']; | |
868 | $contactTypes = $cache->get($cacheKey); | |
869 | if ($contactTypes === NULL) { | |
0b9bbdbc JS |
870 | $query = CRM_Utils_SQL_Select::from('civicrm_contact_type'); |
871 | $dao = CRM_Core_DAO::executeQuery($query->toSQL()); | |
872 | $contactTypes = array_column($dao->fetchAll(), NULL, 'name'); | |
873 | $name_options = self::buildOptions('parent_id', 'validate'); | |
874 | $label_options = self::buildOptions('parent_id', 'get'); | |
ccd901a1 | 875 | foreach ($contactTypes as $id => $contactType) { |
0b9bbdbc JS |
876 | $contactTypes[$id]['parent'] = $contactType['parent_id'] ? $name_options[$contactType['parent_id']] : NULL; |
877 | $contactTypes[$id]['parent_label'] = $contactType['parent_id'] ? $label_options[$contactType['parent_id']] : NULL; | |
8497e902 | 878 | // Cast int/bool types. |
0b9bbdbc JS |
879 | $contactTypes[$id]['id'] = (int) $contactType['id']; |
880 | $contactTypes[$id]['parent_id'] = $contactType['parent_id'] ? (int) $contactType['parent_id'] : NULL; | |
881 | $contactTypes[$id]['is_active'] = (bool) $contactType['is_active']; | |
882 | $contactTypes[$id]['is_reserved'] = (bool) $contactType['is_reserved']; | |
ccd901a1 | 883 | } |
3c33fb16 | 884 | $cache->set($cacheKey, $contactTypes); |
ccd901a1 | 885 | } |
ccd901a1 | 886 | return $contactTypes; |
887 | } | |
888 | ||
6a488035 | 889 | } |