Merge pull request #23536 from eileenmcnaughton/import_cust
[civicrm-core.git] / CRM / Contact / BAO / ContactType.php
CommitLineData
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 17class 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
278SELECT 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
280FROM civicrm_contact_type c
281LEFT JOIN civicrm_contact_type p ON ( c.parent_id = p.id )
282WHERE ( c.name IS NOT NULL )
7fc37770 283';
6a488035
TO
284
285 if ($all === FALSE) {
7fc37770 286 $sql .= '
6a488035
TO
287AND c.is_active = 1
288AND ( 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 = "
365SELECT subtype.name as contact_subtype, type.name as contact_type
366FROM civicrm_contact_type subtype
367INNER JOIN civicrm_contact_type type ON ( subtype.parent_id = type.id )
368WHERE 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 502UPDATE civicrm_contact SET contact_sub_type = NULL
ace511f2
CW
503WHERE contact_sub_type = '$subType'";
504 CRM_Core_DAO::executeQuery($sql);
505 // For contacts with multipe sub-types, remove this one
506 $sql = "
507UPDATE civicrm_contact SET contact_sub_type = REPLACE(contact_sub_type, '$subType', '$sep')
508WHERE 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 = "
715SELECT count(cr.id) FROM civicrm_relationship cr
716INNER JOIN civicrm_relationship_type crt ON
717( cr.relationship_type_id = crt.id {$subTypeClause} )
718WHERE ( cr.contact_id_a = {$contactId} OR cr.contact_id_b = {$contactId} )
719LIMIT 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 750FROM civicrm_custom_group
7fc37770 751WHERE 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.*
828FROM {$tableName} custom
829INNER JOIN civicrm_contact ON civicrm_contact.id = custom.entity_id
830WHERE ($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}