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