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