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