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