3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * Class CRM_Contact_BAO_Relationship.
31 class CRM_Contact_BAO_Relationship
extends CRM_Contact_DAO_Relationship
{
34 * Various constants to indicate different type of relationships.
38 const ALL
= 0, PAST
= 1, DISABLED
= 2, CURRENT
= 4, INACTIVE
= 8;
41 * Constants for is_permission fields.
42 * Note: the slightly non-obvious ordering is due to history...
44 const NONE
= 0, EDIT
= 1, VIEW
= 2;
47 * The list of column headers
50 private static $columnHeaders;
53 * Create function - use the API instead.
55 * Note that the previous create function has been renamed 'legacyCreateMultiple'
56 * and this is new in 4.6
57 * All existing calls have been changed to legacyCreateMultiple except the api call - however, it is recommended
58 * that you call that as the end to end testing here is based on the api & refactoring may still be done.
60 * @param array $params
62 * @return \CRM_Contact_BAO_Relationship
63 * @throws \CRM_Core_Exception
65 public static function create(&$params) {
67 $extendedParams = self
::loadExistingRelationshipDetails($params);
68 // When id is specified we always wan't to update, so we don't need to
69 // check for duplicate relations.
70 if (!isset($params['id']) && self
::checkDuplicateRelationship($extendedParams, $extendedParams['contact_id_a'], $extendedParams['contact_id_b'], CRM_Utils_Array
::value('id', $extendedParams, 0))) {
71 throw new CRM_Core_Exception('Duplicate Relationship');
73 $params = $extendedParams;
74 if (self
::checkValidRelationship($params, $params, 0)) {
75 throw new CRM_Core_Exception('Invalid Relationship');
77 $relationship = self
::add($params);
78 if (!empty($params['contact_id_a'])) {
80 'contactTarget' => $relationship->contact_id_b
,
81 'contact' => $params['contact_id_a'],
84 //CRM-16087 removed additional call to function relatedMemberships which is already called by disableEnableRelationship
85 //resulting in membership being created twice
86 if (array_key_exists('is_active', $params) && empty($params['is_active'])) {
87 $action = CRM_Core_Action
::DISABLE
;
91 $action = CRM_Core_Action
::ENABLE
;
94 $id = empty($params['id']) ?
$relationship->id
: $params['id'];
95 self
::disableEnableRelationship($id, $action, $params, $ids, $active);
98 if (empty($params['skipRecentView'])) {
99 self
::addRecent($params, $relationship);
102 return $relationship;
106 * Create multiple relationships for one contact.
108 * The relationship details are the same for each relationship except the secondary contact
109 * id can be an array.
111 * @param array $params
112 * Parameters for creating multiple relationships.
113 * The parameters are the same as for relationship create function except that the non-primary
114 * end of the relationship should be an array of one or more contact IDs.
115 * @param string $primaryContactLetter
116 * a or b to denote the primary contact for this action. The secondary may be multiple contacts
117 * and should be an array.
120 * @throws \CRM_Core_Exception
122 public static function createMultiple($params, $primaryContactLetter) {
123 $secondaryContactLetter = ($primaryContactLetter == 'a') ?
'b' : 'a';
124 $secondaryContactIDs = $params['contact_id_' . $secondaryContactLetter];
125 $valid = $invalid = $duplicate = $saved = 0;
126 $relationshipIDs = [];
127 foreach ($secondaryContactIDs as $secondaryContactID) {
129 $params['contact_id_' . $secondaryContactLetter] = $secondaryContactID;
130 $relationship = civicrm_api3('relationship', 'create', $params);
131 $relationshipIDs[] = $relationship['id'];
134 catch (CiviCRM_API3_Exception
$e) {
135 switch ($e->getMessage()) {
136 case 'Duplicate Relationship':
140 case 'Invalid Relationship':
145 throw new CRM_Core_Exception('unknown relationship create error ' . $e->getMessage());
152 'invalid' => $invalid,
153 'duplicate' => $duplicate,
155 'relationship_ids' => $relationshipIDs,
160 * Takes an associative array and creates a relationship object.
162 * @deprecated For single creates use the api instead (it's tested).
163 * For multiple a new variant of this function needs to be written and migrated to as this is a bit
166 * @param array $params
167 * (reference ) an assoc array of name/value pairs.
169 * The array that holds all the db ids.
170 * per http://wiki.civicrm.org/confluence/display/CRM/Database+layer
171 * "we are moving away from the $ids param "
174 * @throws \CRM_Core_Exception
176 public static function legacyCreateMultiple(&$params, $ids = []) {
177 $valid = $invalid = $duplicate = $saved = 0;
178 $relationships = $relationshipIds = [];
179 $relationshipId = CRM_Utils_Array
::value('relationship', $ids, CRM_Utils_Array
::value('id', $params));
181 //CRM-9015 - the hooks are called here & in add (since add doesn't call create)
182 // but in future should be tidied per ticket
183 if (empty($relationshipId)) {
190 // @todo pre hook is called from add - remove it from here
191 CRM_Utils_Hook
::pre($hook, 'Relationship', $relationshipId, $params);
193 if (!$relationshipId) {
194 // creating a new relationship
195 $dataExists = self
::dataExists($params);
197 return [FALSE, TRUE, FALSE, FALSE, NULL];
199 $relationshipIds = [];
200 foreach ($params['contact_check'] as $key => $value) {
201 // check if the relationship is valid between contacts.
202 // step 1: check if the relationship is valid if not valid skip and keep the count
203 // step 2: check the if two contacts already have a relationship if yes skip and keep the count
204 // step 3: if valid relationship then add the relation and keep the count
207 $contactFields = self
::setContactABFromIDs($params, $ids, $key);
208 $errors = self
::checkValidRelationship($contactFields, $ids, $key);
214 //CRM-16978:check duplicate relationship as per case id.
215 if ($caseId = CRM_Utils_Array
::value('case_id', $params)) {
216 $contactFields['case_id'] = $caseId;
219 self
::checkDuplicateRelationship(
221 CRM_Utils_Array
::value('contact', $ids),
230 $singleInstanceParams = array_merge($params, $contactFields);
231 $relationship = self
::add($singleInstanceParams);
232 $relationshipIds[] = $relationship->id
;
233 $relationships[$relationship->id
] = $relationship;
236 // editing the relationship
239 // check for duplicate relationship
240 // @todo this code doesn't cope well with updates - causes e-Notices.
241 // API has a lot of code to work around
242 // this but should review this code & remove the extra handling from the api
243 // it seems doubtful any of this is relevant if the contact fields & relationship
244 // type fields are not set
246 self
::checkDuplicateRelationship(
248 CRM_Utils_Array
::value('contact', $ids),
249 $ids['contactTarget'],
254 return [$valid, $invalid, $duplicate, $saved, NULL];
257 $validContacts = TRUE;
258 //validate contacts in update mode also.
259 $contactFields = self
::setContactABFromIDs($params, $ids, $ids['contactTarget']);
260 if (!empty($ids['contact']) && !empty($ids['contactTarget'])) {
261 if (self
::checkValidRelationship($contactFields, $ids, $ids['contactTarget'])) {
262 $validContacts = FALSE;
266 if ($validContacts) {
267 // editing an existing relationship
268 $singleInstanceParams = array_merge($params, $contactFields);
269 $relationship = self
::add($singleInstanceParams, $ids, $ids['contactTarget']);
270 $relationshipIds[] = $relationship->id
;
271 $relationships[$relationship->id
] = $relationship;
276 // do not add to recent items for import, CRM-4399
277 if (!(!empty($params['skipRecentView']) ||
$invalid ||
$duplicate)) {
278 self
::addRecent($params, $relationship);
281 return [$valid, $invalid, $duplicate, $saved, $relationshipIds, $relationships];
285 * This is the function that check/add if the relationship created is valid.
287 * @param array $params
288 * Array of name/value pairs.
290 * The array that holds all the db ids.
291 * @param int $contactId
292 * This is contact id for adding relationship.
294 * @return CRM_Contact_BAO_Relationship
296 * @throws \CiviCRM_API3_Exception
298 public static function add($params, $ids = [], $contactId = NULL) {
299 $params['id'] = CRM_Utils_Array
::value('relationship', $ids, CRM_Utils_Array
::value('id', $params));
305 CRM_Utils_Hook
::pre($hook, 'Relationship', $params['id'], $params);
307 $relationshipTypes = CRM_Utils_Array
::value('relationship_type_id', $params);
308 // explode the string with _ to get the relationship type id
309 // and to know which contact has to be inserted in
310 // contact_id_a and which one in contact_id_b
311 list($relationshipTypeID) = explode('_', $relationshipTypes);
313 $relationship = new CRM_Contact_BAO_Relationship();
314 if (!empty($params['id'])) {
315 $relationship->id
= $params['id'];
316 // Only load the relationship if we're missing required params
317 $requiredParams = ['contact_id_a', 'contact_id_b', 'relationship_type_id'];
318 foreach ($requiredParams as $requiredKey) {
319 if (!isset($params[$requiredKey])) {
320 $relationship->find(TRUE);
326 $relationship->copyValues($params);
327 // @todo we could probably set $params['relationship_type_id'] above but it's unclear
328 // what that would do with the code below this. So for now be conservative and set it manually.
329 if (!empty($relationshipTypeID)) {
330 $relationship->relationship_type_id
= $relationshipTypeID;
333 $params['contact_id_a'] = $relationship->contact_id_a
;
334 $params['contact_id_b'] = $relationship->contact_id_b
;
336 // check if the relationship type is Head of Household then update the
337 // household's primary contact with this contact.
339 $headOfHouseHoldID = civicrm_api3('RelationshipType', 'getvalue', [
341 'name_a_b' => "Head of Household for",
343 if ($relationshipTypeID == $headOfHouseHoldID) {
344 CRM_Contact_BAO_Household
::updatePrimaryContact($relationship->contact_id_b
, $relationship->contact_id_a
);
347 catch (Exception
$e) {
348 // No "Head of Household" relationship found so we skip specific processing
351 if (!empty($params['id']) && self
::isCurrentEmployerNeedingToBeCleared($relationship->toArray(), $params['id'], $relationshipTypeID)) {
352 CRM_Contact_BAO_Contact_Utils
::clearCurrentEmployer($relationship->contact_id_a
);
355 $dateFields = ['end_date', 'start_date'];
357 foreach (self
::getdefaults() as $defaultField => $defaultValue) {
358 if (isset($params[$defaultField])) {
359 if (in_array($defaultField, $dateFields)) {
360 $relationship->$defaultField = CRM_Utils_Date
::format(CRM_Utils_Array
::value($defaultField, $params));
361 if (!$relationship->$defaultField) {
362 $relationship->$defaultField = 'NULL';
366 $relationship->$defaultField = $params[$defaultField];
369 elseif (empty($params['id'])) {
370 $relationship->$defaultField = $defaultValue;
374 $relationship->save();
375 // is_current_employer is an optional parameter that triggers updating the employer_id field to reflect
376 // the relationship being updated. As of writing only truthy versions of the parameter are respected.
377 // https://github.com/civicrm/civicrm-core/pull/13331 attempted to cover both but stalled in QA
378 // so currently we have a cut down version.
379 if (!empty($params['is_current_employer'])) {
380 if (!$relationship->relationship_type_id ||
!$relationship->contact_id_a ||
!$relationship->contact_id_b
) {
381 $relationship->fetch();
383 if (self
::isRelationshipTypeCurrentEmployer($relationship->relationship_type_id
)) {
384 CRM_Contact_BAO_Contact_Utils
::setCurrentEmployer([$relationship->contact_id_a
=> $relationship->contact_id_b
]);
387 // add custom field values
388 if (!empty($params['custom'])) {
389 CRM_Core_BAO_CustomValueTable
::store($params['custom'], 'civicrm_relationship', $relationship->id
);
392 CRM_Utils_Hook
::post($hook, 'Relationship', $relationship->id
, $relationship);
394 return $relationship;
398 * Add relationship to recent links.
400 * @param array $params
401 * @param CRM_Contact_DAO_Relationship $relationship
403 public static function addRecent($params, $relationship) {
404 $url = CRM_Utils_System
::url('civicrm/contact/view/rel',
405 "action=view&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&context=home"
407 $session = CRM_Core_Session
::singleton();
409 if (($session->get('userID') == $relationship->contact_id_a
) ||
410 CRM_Contact_BAO_Contact_Permission
::allow($relationship->contact_id_a
, CRM_Core_Permission
::EDIT
)
412 $rType = substr(CRM_Utils_Array
::value('relationship_type_id', $params), -3);
414 'editUrl' => CRM_Utils_System
::url('civicrm/contact/view/rel',
415 "action=update&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home"
417 'deleteUrl' => CRM_Utils_System
::url('civicrm/contact/view/rel',
418 "action=delete&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home"
422 $title = CRM_Contact_BAO_Contact
::displayName($relationship->contact_id_a
) . ' (' . CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType',
423 $relationship->relationship_type_id
, 'label_a_b'
424 ) . ' ' . CRM_Contact_BAO_Contact
::displayName($relationship->contact_id_b
) . ')';
426 CRM_Utils_Recent
::add($title,
430 $relationship->contact_id_a
,
437 * Load contact ids and relationship type id when doing a create call if not provided.
439 * There are are various checks done in create which require this information which is optional
442 * @param array $params
443 * Parameters passed to create call.
446 * Parameters with missing fields added if required.
448 public static function loadExistingRelationshipDetails($params) {
449 if (!empty($params['contact_id_a'])
450 && !empty($params['contact_id_b'])
451 && is_numeric($params['relationship_type_id'])) {
454 if (empty($params['id'])) {
458 $fieldsToFill = ['contact_id_a', 'contact_id_b', 'relationship_type_id'];
459 $result = CRM_Core_DAO
::executeQuery("SELECT " . implode(',', $fieldsToFill) . " FROM civicrm_relationship WHERE id = %1", [
465 while ($result->fetch()) {
466 foreach ($fieldsToFill as $field) {
467 $params[$field] = !empty($params[$field]) ?
$params[$field] : $result->$field;
474 * Resolve passed in contact IDs to contact_id_a & contact_id_b.
476 * @param array $params
478 * @param null $contactID
481 * @throws \CRM_Core_Exception
483 public static function setContactABFromIDs($params, $ids = [], $contactID = NULL) {
486 // $ids['contact'] is deprecated but comes from legacyCreateMultiple function.
487 if (empty($ids['contact'])) {
488 if (!empty($params['id'])) {
489 return self
::loadExistingRelationshipDetails($params);
491 throw new CRM_Core_Exception('Cannot create relationship, insufficient contact IDs provided');
493 if (isset($params['relationship_type_id']) && !is_numeric($params['relationship_type_id'])) {
494 $relationshipTypes = CRM_Utils_Array
::value('relationship_type_id', $params);
495 list($relationshipTypeID, $first) = explode('_', $relationshipTypes);
496 $returnFields['relationship_type_id'] = $relationshipTypeID;
498 foreach (['a', 'b'] as $contactLetter) {
499 if (empty($params['contact_' . $contactLetter])) {
500 if ($first == $contactLetter) {
501 $returnFields['contact_id_' . $contactLetter] = CRM_Utils_Array
::value('contact', $ids);
504 $returnFields['contact_id_' . $contactLetter] = $contactID;
510 return $returnFields;
514 * Specify defaults for creating a relationship.
517 * array of defaults for creating relationship
519 public static function getdefaults() {
522 'is_permission_a_b' => self
::NONE
,
523 'is_permission_b_a' => self
::NONE
,
525 'start_date' => 'NULL',
527 'end_date' => 'NULL',
532 * Check if there is data to create the object.
534 * @param array $params
535 * (reference ) an assoc array of name/value pairs.
539 public static function dataExists(&$params) {
540 // return if no data present
541 if (!is_array(CRM_Utils_Array
::value('contact_check', $params))) {
548 * Get get list of relationship type based on the contact type.
550 * @param int $contactId
551 * This is the contact id of the current contact.
552 * @param null $contactSuffix
553 * @param string $relationshipId
554 * The id of the existing relationship if any.
555 * @param string $contactType
558 * If true returns relationship types in both the direction.
559 * @param string $column
560 * Name/label that going to retrieve from db.
561 * @param bool $biDirectional
562 * @param array $contactSubType
563 * Includes relationship types between this subtype.
564 * @param bool $onlySubTypeRelationTypes
565 * If set only subtype which is passed by $contactSubType
566 * related relationship types get return
569 * array reference of all relationship types with context to current contact.
571 public static function getContactRelationshipType(
573 $contactSuffix = NULL,
574 $relationshipId = NULL,
578 $biDirectional = TRUE,
579 $contactSubType = NULL,
580 $onlySubTypeRelationTypes = FALSE
583 $relationshipType = [];
584 $allRelationshipType = CRM_Core_PseudoConstant
::relationshipType($column);
586 $otherContactType = NULL;
587 if ($relationshipId) {
588 $relationship = new CRM_Contact_DAO_Relationship();
589 $relationship->id
= $relationshipId;
590 if ($relationship->find(TRUE)) {
591 $contact = new CRM_Contact_DAO_Contact();
592 $contact->id
= ($relationship->contact_id_a
== $contactId) ?
$relationship->contact_id_b
: $relationship->contact_id_a
;
594 if ($contact->find(TRUE)) {
595 $otherContactType = $contact->contact_type
;
596 //CRM-5125 for contact subtype specific relationshiptypes
597 if ($contact->contact_sub_type
) {
598 $otherContactSubType = $contact->contact_sub_type
;
604 $contactSubType = (array) $contactSubType;
606 $contactType = CRM_Contact_BAO_Contact
::getContactType($contactId);
607 $contactSubType = CRM_Contact_BAO_Contact
::getContactSubType($contactId);
610 foreach ($allRelationshipType as $key => $value) {
611 // the contact type is required or matches
612 if (((!$value['contact_type_a']) ||
613 $value['contact_type_a'] == $contactType
615 // the other contact type is required or present or matches
616 ((!$value['contact_type_b']) ||
617 (!$otherContactType) ||
618 $value['contact_type_b'] == $otherContactType
620 (in_array($value['contact_sub_type_a'], $contactSubType) ||
621 (!$value['contact_sub_type_a'] && !$onlySubTypeRelationTypes)
624 $relationshipType[$key . '_a_b'] = $value["{$column}_a_b"];
627 if (((!$value['contact_type_b']) ||
628 $value['contact_type_b'] == $contactType
630 ((!$value['contact_type_a']) ||
631 (!$otherContactType) ||
632 $value['contact_type_a'] == $otherContactType
634 (in_array($value['contact_sub_type_b'], $contactSubType) ||
635 (!$value['contact_sub_type_b'] && !$onlySubTypeRelationTypes)
638 $relationshipType[$key . '_b_a'] = $value["{$column}_b_a"];
642 $relationshipType[$key . '_a_b'] = $value["{$column}_a_b"];
643 $relationshipType[$key . '_b_a'] = $value["{$column}_b_a"];
647 if ($biDirectional) {
648 $relationshipType = self
::removeRelationshipTypeDuplicates($relationshipType, $contactSuffix);
651 // sort the relationshipType in ascending order CRM-7736
652 asort($relationshipType);
653 return $relationshipType;
657 * Given a list of relationship types, return the list with duplicate types
658 * removed, being careful to retain only the duplicate which matches the given
659 * 'a_b' or 'b_a' suffix.
661 * @param array $relationshipTypeList A list of relationship types, in the format
662 * returned by self::getContactRelationshipType().
663 * @param string $suffix Either 'a_b' or 'b_a'; defaults to 'a_b'
665 * @return array The modified value of $relationshipType
667 public static function removeRelationshipTypeDuplicates($relationshipTypeList, $suffix = NULL) {
668 if (empty($suffix)) {
672 // Find those labels which are listed more than once.
673 $duplicateValues = array_diff_assoc($relationshipTypeList, array_unique($relationshipTypeList));
675 // For each duplicate label, find its keys, and remove from $relationshipType
676 // the key which does not match $suffix.
677 foreach ($duplicateValues as $value) {
678 $keys = array_keys($relationshipTypeList, $value);
679 foreach ($keys as $key) {
680 if (substr($key, -3) != $suffix) {
681 unset($relationshipTypeList[$key]);
685 return $relationshipTypeList;
689 * Delete current employer relationship.
694 * @return CRM_Contact_DAO_Relationship
696 public static function clearCurrentEmployer($id, $action) {
697 $relationship = new CRM_Contact_DAO_Relationship();
698 $relationship->id
= $id;
699 $relationship->find(TRUE);
701 //to delete relationship between household and individual \
702 //or between individual and organization
703 if (($action & CRM_Core_Action
::DISABLE
) ||
($action & CRM_Core_Action
::DELETE
)) {
704 $relTypes = CRM_Utils_Array
::index(['name_a_b'], CRM_Core_PseudoConstant
::relationshipType('name'));
706 (isset($relTypes['Employee of']) && $relationship->relationship_type_id
== $relTypes['Employee of']['id']) ||
707 (isset($relTypes['Household Member of']) && $relationship->relationship_type_id
== $relTypes['Household Member of']['id'])
709 $sharedContact = new CRM_Contact_DAO_Contact();
710 $sharedContact->id
= $relationship->contact_id_a
;
711 $sharedContact->find(TRUE);
714 // changed FROM "...relationship->relationship_type_id == 4..." TO "...relationship->relationship_type_id == 5..."
715 // As the system should be looking for type "employer of" (id 5) and not "sibling of" (id 4)
716 // As suggested by @davecivicrm, the employee relationship type id is fetched using the CRM_Core_DAO::getFieldValue() class and method, since these ids differ from system to system.
717 $employerRelTypeId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
719 if ($relationship->relationship_type_id
== $employerRelTypeId && $relationship->contact_id_b
== $sharedContact->employer_id
) {
720 CRM_Contact_BAO_Contact_Utils
::clearCurrentEmployer($relationship->contact_id_a
);
725 return $relationship;
729 * Delete the relationship.
735 * @throws \CRM_Core_Exception
737 public static function del($id) {
738 // delete from relationship table
739 CRM_Utils_Hook
::pre('delete', 'Relationship', $id, CRM_Core_DAO
::$_nullArray);
741 $relationship = self
::clearCurrentEmployer($id, CRM_Core_Action
::DELETE
);
742 if (CRM_Core_Permission
::access('CiviMember')) {
743 // create $params array which isrequired to delete memberships
744 // of the related contacts.
746 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b",
747 'contact_check' => [$relationship->contact_id_b
=> 1],
751 // calling relatedMemberships to delete the memberships of
753 self
::relatedMemberships($relationship->contact_id_a
,
756 CRM_Core_Action
::DELETE
,
761 $relationship->delete();
762 CRM_Core_Session
::setStatus(ts('Selected relationship has been deleted successfully.'), ts('Record Deleted'), 'success');
764 CRM_Utils_Hook
::post('delete', 'Relationship', $id, $relationship);
766 // delete the recently created Relationship
767 $relationshipRecent = [
769 'type' => 'Relationship',
771 CRM_Utils_Recent
::del($relationshipRecent);
773 return $relationship;
777 * Disable/enable the relationship.
783 * @param array $params
785 * @param bool $active
787 * @throws \CRM_Core_Exception
789 public static function disableEnableRelationship($id, $action, $params = [], $ids = [], $active = FALSE) {
790 $relationship = self
::clearCurrentEmployer($id, $action);
793 // create $params array which is required to delete memberships
794 // of the related contacts.
795 if (empty($params)) {
797 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b",
798 'contact_check' => [$relationship->contact_id_b
=> 1],
801 $contact_id_a = empty($params['contact_id_a']) ?
$relationship->contact_id_a
: $params['contact_id_a'];
802 // calling relatedMemberships to delete/add the memberships of
804 if ($action & CRM_Core_Action
::DISABLE
) {
805 CRM_Contact_BAO_Relationship
::relatedMemberships($contact_id_a,
808 CRM_Core_Action
::DELETE
,
812 elseif ($action & CRM_Core_Action
::ENABLE
) {
813 $ids['contact'] = empty($ids['contact']) ?
$contact_id_a : $ids['contact'];
814 CRM_Contact_BAO_Relationship
::relatedMemberships($contact_id_a,
817 empty($params['id']) ? CRM_Core_Action
::ADD
: CRM_Core_Action
::UPDATE
,
825 * Delete the object records that are associated with this contact.
827 * @param int $contactId
828 * Id of the contact to delete.
830 public static function deleteContact($contactId) {
831 $relationship = new CRM_Contact_DAO_Relationship();
832 $relationship->contact_id_a
= $contactId;
833 $relationship->delete();
835 $relationship = new CRM_Contact_DAO_Relationship();
836 $relationship->contact_id_b
= $contactId;
837 $relationship->delete();
839 CRM_Contact_BAO_Household
::updatePrimaryContact(NULL, $contactId);
843 * Get the other contact in a relationship.
848 * $returns returns the contact ids in the realtionship
850 * @return \CRM_Contact_DAO_Relationship
852 public static function getRelationshipByID($id) {
853 $relationship = new CRM_Contact_DAO_Relationship();
855 $relationship->id
= $id;
856 $relationship->selectAdd();
857 $relationship->selectAdd('contact_id_a, contact_id_b');
858 $relationship->find(TRUE);
860 return $relationship;
864 * Check if the relationship type selected between two contacts is correct.
866 * @param int $contact_a
868 * @param int $contact_b
870 * @param int $relationshipTypeId
871 * Relationship type id.
874 * true if it is valid relationship else false
876 public static function checkRelationshipType($contact_a, $contact_b, $relationshipTypeId) {
877 $relationshipType = new CRM_Contact_DAO_RelationshipType();
878 $relationshipType->id
= $relationshipTypeId;
879 $relationshipType->selectAdd();
880 $relationshipType->selectAdd('contact_type_a, contact_type_b, contact_sub_type_a, contact_sub_type_b');
881 if ($relationshipType->find(TRUE)) {
882 $contact_type_a = CRM_Contact_BAO_Contact
::getContactType($contact_a);
883 $contact_type_b = CRM_Contact_BAO_Contact
::getContactType($contact_b);
885 $contact_sub_type_a = CRM_Contact_BAO_Contact
::getContactSubType($contact_a);
886 $contact_sub_type_b = CRM_Contact_BAO_Contact
::getContactSubType($contact_b);
888 if (((!$relationshipType->contact_type_a
) ||
($relationshipType->contact_type_a
== $contact_type_a)) &&
889 ((!$relationshipType->contact_type_b
) ||
($relationshipType->contact_type_b
== $contact_type_b)) &&
890 ((!$relationshipType->contact_sub_type_a
) ||
(in_array($relationshipType->contact_sub_type_a
,
893 ((!$relationshipType->contact_sub_type_b
) ||
(in_array($relationshipType->contact_sub_type_b
,
907 * This function does the validtion for valid relationship.
909 * @param array $params
910 * This array contains the values there are subitted by the form.
912 * The array that holds all the db ids.
913 * @param int $contactId
914 * This is contact id for adding relationship.
918 public static function checkValidRelationship($params, $ids, $contactId) {
920 // function to check if the relationship selected is correct
921 // i.e. employer relationship can exit between Individual and Organization (not between Individual and Individual)
922 if (!CRM_Contact_BAO_Relationship
::checkRelationshipType($params['contact_id_a'], $params['contact_id_b'],
923 $params['relationship_type_id'])) {
924 $errors = 'Please select valid relationship between these two contacts.';
930 * This function checks for duplicate relationship.
932 * @param array $params
933 * (reference ) an assoc array of name/value pairs.
935 * This the id of the contact whom we are adding relationship.
936 * @param int $contactId
937 * This is contact id for adding relationship.
938 * @param int $relationshipId
939 * This is relationship id for the contact.
942 * true if record exists else false
944 public static function checkDuplicateRelationship(&$params, $id, $contactId = 0, $relationshipId = 0) {
945 $relationshipTypeId = CRM_Utils_Array
::value('relationship_type_id', $params);
946 list($type) = explode('_', $relationshipTypeId);
950 FROM civicrm_relationship
951 WHERE relationship_type_id = " . CRM_Utils_Type
::escape($type, 'Integer');
954 * CRM-11792 - date fields from API are in ISO format, but this function
955 * supports date arrays BAO has increasingly standardised to ISO format
956 * so I believe this function should support ISO rather than make API
957 * format it - however, need to support array format for now to avoid breakage
958 * @ time of writing this function is called from Relationship::legacyCreateMultiple (twice)
959 * CRM_BAO_Contact_Utils::clearCurrentEmployer (seemingly without dates)
960 * CRM_Contact_Form_Task_AddToOrganization::postProcess &
961 * CRM_Contact_Form_Task_AddToHousehold::postProcess
962 * (I don't think the last 2 support dates but not sure
965 $dateFields = ['end_date', 'start_date'];
966 foreach ($dateFields as $dateField) {
967 if (array_key_exists($dateField, $params)) {
968 if (empty($params[$dateField]) ||
$params[$dateField] == 'null') {
969 //this is most likely coming from an api call & probably loaded
970 // from the DB to deal with some of the
971 // other myriad of excessive checks still in place both in
972 // the api & the create functions
973 $queryString .= " AND $dateField IS NULL";
976 elseif (is_array($params[$dateField])) {
977 $queryString .= " AND $dateField = " .
978 CRM_Utils_Type
::escape(CRM_Utils_Date
::format($params[$dateField]), 'Date');
981 $queryString .= " AND $dateField = " .
982 CRM_Utils_Type
::escape($params[$dateField], 'Date');
988 " AND ( ( contact_id_a = " . CRM_Utils_Type
::escape($id, 'Integer') .
989 " AND contact_id_b = " . CRM_Utils_Type
::escape($contactId, 'Integer') .
990 " ) OR ( contact_id_a = " . CRM_Utils_Type
::escape($contactId, 'Integer') .
991 " AND contact_id_b = " . CRM_Utils_Type
::escape($id, 'Integer') . " ) ) ";
993 //if caseId is provided, include it duplicate checking.
994 if ($caseId = CRM_Utils_Array
::value('case_id', $params)) {
995 $queryString .= " AND case_id = " . CRM_Utils_Type
::escape($caseId, 'Integer');
998 if ($relationshipId) {
999 $queryString .= " AND id !=" . CRM_Utils_Type
::escape($relationshipId, 'Integer');
1002 $relationship = new CRM_Contact_BAO_Relationship();
1003 $relationship->query($queryString);
1004 while ($relationship->fetch()) {
1005 // Check whether the custom field values are identical.
1006 $result = self
::checkDuplicateCustomFields($params, $relationship->id
);
1015 * this function checks whether the values of the custom fields in $params are
1016 * the same as the values of the custom fields of the relation with given
1019 * @param array $params (reference) an assoc array of name/value pairs
1020 * @param int $relationshipId ID of an existing duplicate relation
1022 * @return boolean true if custom field values are identical
1026 private static function checkDuplicateCustomFields(&$params, $relationshipId) {
1027 // Get the custom values of the existing relationship.
1028 $existingValues = CRM_Core_BAO_CustomValueTable
::getEntityValues($relationshipId, 'Relationship');
1029 // Create a similar array for the new relationship.
1031 if (array_key_exists('custom', $params)) {
1032 // $params['custom'] seems to be an array. Each value is again an array.
1033 // This array contains one value (key -1), and this value seems to be
1034 // an array with the information about the custom value.
1035 foreach ($params['custom'] as $value) {
1036 foreach ($value as $customValue) {
1037 $newValues[$customValue['custom_field_id']] = $customValue['value'];
1042 // Calculate difference between arrays. If the only key-value pairs
1043 // that are in one array but not in the other are empty, the
1044 // custom fields are considered to be equal.
1045 // See https://github.com/civicrm/civicrm-core/pull/6515#issuecomment-137985667
1046 $diff1 = array_diff_assoc($existingValues, $newValues);
1047 $diff2 = array_diff_assoc($newValues, $existingValues);
1049 return !array_filter($diff1) && !array_filter($diff2);
1053 * Update the is_active flag in the db.
1056 * Id of the database record.
1057 * @param bool $is_active
1058 * Value we want to set the is_active field.
1062 * @throws CiviCRM_API3_Exception
1064 public static function setIsActive($id, $is_active) {
1065 // as both the create & add functions have a bunch of logic in them that
1066 // doesn't seem to cope with a normal update we will call the api which
1067 // has tested handling for this
1068 // however, a longer term solution would be to simplify the add, create & api functions
1069 // to be more standard. It is debatable @ that point whether it's better to call the BAO
1070 // direct as the api is more tested.
1071 $result = civicrm_api('relationship', 'create', [
1073 'is_active' => $is_active,
1077 if (is_array($result) && !empty($result['is_error']) && $result['error_message'] != 'Duplicate Relationship') {
1078 throw new CiviCRM_API3_Exception($result['error_message'], CRM_Utils_Array
::value('error_code', $result, 'undefined'), $result);
1085 * Fetch a relationship object and store the values in the values array.
1087 * @param array $params
1088 * Input parameters to find object.
1089 * @param array $values
1090 * Output values of the object.
1093 * (reference) the values that could be potentially assigned to smarty
1095 public static function &getValues(&$params, &$values) {
1096 if (empty($params)) {
1101 // get the specific number of relationship or all relationships.
1102 if (!empty($params['numRelationship'])) {
1103 $v['data'] = &CRM_Contact_BAO_Relationship
::getRelationship($params['contact_id'], NULL, $params['numRelationship']);
1106 $v['data'] = CRM_Contact_BAO_Relationship
::getRelationship($params['contact_id']);
1109 // get the total count of relationships
1110 $v['totalCount'] = count($v['data']);
1112 $values['relationship']['data'] = &$v['data'];
1113 $values['relationship']['totalCount'] = &$v['totalCount'];
1119 * Helper function to form the sql for relationship retrieval.
1121 * @param int $contactId
1123 * @param int $status
1124 * (check const at top of file).
1125 * @param int $numRelationship
1126 * No of relationships to display (limit).
1128 * Get the no of relationships.
1129 * $param int $relationshipId relationship id
1130 * @param int $relationshipId
1131 * @param string $direction
1132 * The direction we are interested in a_b or b_a.
1133 * @param array $params
1134 * Array of extra values including relationship_type_id per api spec.
1137 * [select, from, where]
1138 * @throws \Exception
1140 public static function makeURLClause($contactId, $status, $numRelationship, $count, $relationshipId, $direction, $params = []) {
1141 $select = $from = $where = '';
1145 if ($direction == 'a_b') {
1146 $select .= ' SELECT count(DISTINCT civicrm_relationship.id) as cnt1, 0 as cnt2 ';
1149 $select .= ' SELECT 0 as cnt1, count(DISTINCT civicrm_relationship.id) as cnt2 ';
1153 $select .= ' SELECT civicrm_relationship.id as civicrm_relationship_id,
1154 civicrm_contact.sort_name as sort_name,
1155 civicrm_contact.display_name as display_name,
1156 civicrm_contact.job_title as job_title,
1157 civicrm_contact.employer_id as employer_id,
1158 civicrm_contact.organization_name as organization_name,
1159 civicrm_address.street_address as street_address,
1160 civicrm_address.city as city,
1161 civicrm_address.postal_code as postal_code,
1162 civicrm_state_province.abbreviation as state,
1163 civicrm_country.name as country,
1164 civicrm_email.email as email,
1165 civicrm_contact.contact_type as contact_type,
1166 civicrm_contact.contact_sub_type as contact_sub_type,
1167 civicrm_phone.phone as phone,
1168 civicrm_contact.id as civicrm_contact_id,
1169 civicrm_relationship.contact_id_b as contact_id_b,
1170 civicrm_relationship.contact_id_a as contact_id_a,
1171 civicrm_relationship_type.id as civicrm_relationship_type_id,
1172 civicrm_relationship.start_date as start_date,
1173 civicrm_relationship.end_date as end_date,
1174 civicrm_relationship.description as description,
1175 civicrm_relationship.is_active as is_active,
1176 civicrm_relationship.is_permission_a_b as is_permission_a_b,
1177 civicrm_relationship.is_permission_b_a as is_permission_b_a,
1178 civicrm_relationship.case_id as case_id';
1180 if ($direction == 'a_b') {
1181 $select .= ', civicrm_relationship_type.label_a_b as label_a_b,
1182 civicrm_relationship_type.label_b_a as relation ';
1185 $select .= ', civicrm_relationship_type.label_a_b as label_a_b,
1186 civicrm_relationship_type.label_a_b as relation ';
1191 FROM civicrm_relationship
1192 INNER JOIN civicrm_relationship_type ON ( civicrm_relationship.relationship_type_id = civicrm_relationship_type.id )
1193 INNER JOIN civicrm_contact ";
1194 if ($direction == 'a_b') {
1195 $from .= 'ON ( civicrm_contact.id = civicrm_relationship.contact_id_a ) ';
1198 $from .= 'ON ( civicrm_contact.id = civicrm_relationship.contact_id_b ) ';
1203 LEFT JOIN civicrm_address ON (civicrm_address.contact_id = civicrm_contact.id AND civicrm_address.is_primary = 1)
1204 LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = civicrm_contact.id AND civicrm_phone.is_primary = 1)
1205 LEFT JOIN civicrm_email ON (civicrm_email.contact_id = civicrm_contact.id AND civicrm_email.is_primary = 1)
1206 LEFT JOIN civicrm_state_province ON (civicrm_address.state_province_id = civicrm_state_province.id)
1207 LEFT JOIN civicrm_country ON (civicrm_address.country_id = civicrm_country.id)
1211 $where = 'WHERE ( 1 )';
1213 if ($direction == 'a_b') {
1214 $where .= ' AND civicrm_relationship.contact_id_b = ' . CRM_Utils_Type
::escape($contactId, 'Positive');
1217 $where .= ' AND civicrm_relationship.contact_id_a = ' . CRM_Utils_Type
::escape($contactId, 'Positive') . '
1218 AND civicrm_relationship.contact_id_a != civicrm_relationship.contact_id_b ';
1221 if ($relationshipId) {
1222 $where .= ' AND civicrm_relationship.id = ' . CRM_Utils_Type
::escape($relationshipId, 'Positive');
1225 $date = date('Y-m-d');
1226 if ($status == self
::PAST
) {
1227 //this case for showing past relationship
1228 $where .= ' AND civicrm_relationship.is_active = 1 ';
1229 $where .= " AND civicrm_relationship.end_date < '" . $date . "'";
1231 elseif ($status == self
::DISABLED
) {
1232 // this case for showing disabled relationship
1233 $where .= ' AND civicrm_relationship.is_active = 0 ';
1235 elseif ($status == self
::CURRENT
) {
1236 //this case for showing current relationship
1237 $where .= ' AND civicrm_relationship.is_active = 1 ';
1238 $where .= " AND (civicrm_relationship.end_date >= '" . $date . "' OR civicrm_relationship.end_date IS NULL) ";
1240 elseif ($status == self
::INACTIVE
) {
1241 //this case for showing inactive relationships
1242 $where .= " AND (civicrm_relationship.end_date < '" . $date . "'";
1243 $where .= ' OR civicrm_relationship.is_active = 0 )';
1247 $where .= ' AND civicrm_contact.is_deleted = 0';
1248 if (!empty($params['membership_type_id']) && empty($params['relationship_type_id'])) {
1249 $where .= self
::membershipTypeToRelationshipTypes($params, $direction);
1251 if (!empty($params['relationship_type_id'])) {
1252 if (is_array($params['relationship_type_id'])) {
1253 $where .= " AND " . CRM_Core_DAO
::createSQLFilter('relationship_type_id', $params['relationship_type_id'], 'Integer');
1256 $where .= ' AND relationship_type_id = ' . CRM_Utils_Type
::escape($params['relationship_type_id'], 'Positive');
1259 if ($direction == 'a_b') {
1260 $where .= ' ) UNION ';
1266 return [$select, $from, $where];
1270 * Get a list of relationships.
1272 * @param int $contactId
1274 * @param int $status
1275 * 1: Past 2: Disabled 3: Current.
1276 * @param int $numRelationship
1277 * No of relationships to display (limit).
1279 * Get the no of relationships.
1280 * @param int $relationshipId
1281 * @param array $links
1282 * the list of links to display
1283 * @param int $permissionMask
1284 * the permission mask to be applied for the actions
1285 * @param bool $permissionedContact
1286 * to return only permissioned Contact
1287 * @param array $params
1288 * @param bool $includeTotalCount
1289 * Should we return a count of total accessable relationships
1292 * relationship records
1293 * @throws \Exception
1295 public static function getRelationship(
1297 $status = 0, $numRelationship = 0,
1298 $count = 0, $relationshipId = 0,
1299 $links = NULL, $permissionMask = NULL,
1300 $permissionedContact = FALSE,
1301 $params = [], $includeTotalCount = FALSE
1304 if (!$contactId && !$relationshipId) {
1308 list($select1, $from1, $where1) = self
::makeURLClause($contactId, $status, $numRelationship,
1309 $count, $relationshipId, 'a_b', $params
1311 list($select2, $from2, $where2) = self
::makeURLClause($contactId, $status, $numRelationship,
1312 $count, $relationshipId, 'b_a', $params
1315 $order = $limit = '';
1317 if (empty($params['sort'])) {
1318 $order = ' ORDER BY civicrm_relationship_type_id, sort_name ';
1321 $order = " ORDER BY {$params['sort']} ";
1325 if (!empty($params['offset']) && $params['offset'] > 0) {
1326 $offset = $params['offset'];
1329 if ($numRelationship) {
1330 $limit = " LIMIT {$offset}, $numRelationship";
1334 // building the query string
1335 $queryString = $select1 . $from1 . $where1 . $select2 . $from2 . $where2;
1337 $relationship = new CRM_Contact_DAO_Relationship();
1339 $relationship->query($queryString . $order . $limit);
1342 $relationshipCount = 0;
1343 while ($relationship->fetch()) {
1344 $relationshipCount +
= $relationship->cnt1 +
$relationship->cnt2
;
1346 return $relationshipCount;
1350 if ($includeTotalCount) {
1351 $values['total_relationships'] = CRM_Core_DAO
::singleValueQuery("SELECT count(*) FROM ({$queryString}) AS r");
1355 if ($status != self
::INACTIVE
) {
1357 $mask = array_sum(array_keys($links));
1358 if ($mask & CRM_Core_Action
::DISABLE
) {
1359 $mask -= CRM_Core_Action
::DISABLE
;
1361 if ($mask & CRM_Core_Action
::ENABLE
) {
1362 $mask -= CRM_Core_Action
::ENABLE
;
1365 if ($status == self
::CURRENT
) {
1366 $mask |
= CRM_Core_Action
::DISABLE
;
1368 elseif ($status == self
::DISABLED
) {
1369 $mask |
= CRM_Core_Action
::ENABLE
;
1372 // temporary hold the value of $mask.
1376 while ($relationship->fetch()) {
1377 $rid = $relationship->civicrm_relationship_id
;
1378 $cid = $relationship->civicrm_contact_id
;
1380 if ($permissionedContact &&
1381 (!CRM_Contact_BAO_Contact_Permission
::allow($cid))
1385 if ($status != self
::INACTIVE
&& $links) {
1386 // assign the original value to $mask
1388 // display action links if $cid has edit permission for the relationship.
1389 if (!($permissionMask & CRM_Core_Permission
::EDIT
) && CRM_Contact_BAO_Contact_Permission
::allow($cid, CRM_Core_Permission
::EDIT
)) {
1390 $permissions[] = CRM_Core_Permission
::EDIT
;
1391 $permissions[] = CRM_Core_Permission
::DELETE
;
1392 $permissionMask = CRM_Core_Action
::mask($permissions);
1394 $mask = $mask & $permissionMask;
1396 $values[$rid]['id'] = $rid;
1397 $values[$rid]['cid'] = $cid;
1398 $values[$rid]['contact_id_a'] = $relationship->contact_id_a
;
1399 $values[$rid]['contact_id_b'] = $relationship->contact_id_b
;
1400 $values[$rid]['contact_type'] = $relationship->contact_type
;
1401 $values[$rid]['contact_sub_type'] = $relationship->contact_sub_type
;
1402 $values[$rid]['relationship_type_id'] = $relationship->civicrm_relationship_type_id
;
1403 $values[$rid]['relation'] = $relationship->relation
;
1404 $values[$rid]['name'] = $relationship->sort_name
;
1405 $values[$rid]['display_name'] = $relationship->display_name
;
1406 $values[$rid]['job_title'] = $relationship->job_title
;
1407 $values[$rid]['email'] = $relationship->email
;
1408 $values[$rid]['phone'] = $relationship->phone
;
1409 $values[$rid]['employer_id'] = $relationship->employer_id
;
1410 $values[$rid]['organization_name'] = $relationship->organization_name
;
1411 $values[$rid]['country'] = $relationship->country
;
1412 $values[$rid]['city'] = $relationship->city
;
1413 $values[$rid]['state'] = $relationship->state
;
1414 $values[$rid]['start_date'] = $relationship->start_date
;
1415 $values[$rid]['end_date'] = $relationship->end_date
;
1416 $values[$rid]['description'] = $relationship->description
;
1417 $values[$rid]['is_active'] = $relationship->is_active
;
1418 $values[$rid]['is_permission_a_b'] = $relationship->is_permission_a_b
;
1419 $values[$rid]['is_permission_b_a'] = $relationship->is_permission_b_a
;
1420 $values[$rid]['case_id'] = $relationship->case_id
;
1423 $values[$rid]['status'] = $status;
1426 $values[$rid]['civicrm_relationship_type_id'] = $relationship->civicrm_relationship_type_id
;
1428 if ($relationship->contact_id_a
== $contactId) {
1429 $values[$rid]['rtype'] = 'a_b';
1432 $values[$rid]['rtype'] = 'b_a';
1438 'rtype' => $values[$rid]['rtype'],
1439 'cid' => $contactId,
1440 'cbid' => $values[$rid]['cid'],
1441 'caseid' => $values[$rid]['case_id'],
1442 'clientid' => $contactId,
1445 if ($status == self
::INACTIVE
) {
1446 // setting links for inactive relationships
1447 $mask = array_sum(array_keys($links));
1448 if (!$values[$rid]['is_active']) {
1449 $mask -= CRM_Core_Action
::DISABLE
;
1452 $mask -= CRM_Core_Action
::ENABLE
;
1453 $mask -= CRM_Core_Action
::DISABLE
;
1455 $mask = $mask & $permissionMask;
1458 // Give access to manage case link by copying to MAX_ACTION index temporarily, depending on case permission of user.
1459 if ($values[$rid]['case_id']) {
1460 // Borrowed logic from CRM_Case_Page_Tab
1461 $hasCaseAccess = FALSE;
1462 if (CRM_Core_Permission
::check('access all cases and activities')) {
1463 $hasCaseAccess = TRUE;
1466 $userCases = CRM_Case_BAO_Case
::getCases(FALSE);
1467 if (array_key_exists($values[$rid]['case_id'], $userCases)) {
1468 $hasCaseAccess = TRUE;
1472 if ($hasCaseAccess) {
1473 // give access by copying to MAX_ACTION temporarily, otherwise leave at NONE which won't display
1474 $links[CRM_Core_Action
::MAX_ACTION
] = $links[CRM_Core_Action
::NONE
];
1475 $links[CRM_Core_Action
::MAX_ACTION
]['name'] = ts('Manage Case #%1', [1 => $values[$rid]['case_id']]);
1476 $links[CRM_Core_Action
::MAX_ACTION
]['class'] = 'no-popup';
1478 // Also make sure we have the right client cid since can get here from multiple relationship tabs.
1479 if ($values[$rid]['rtype'] == 'b_a') {
1480 $replace['clientid'] = $values[$rid]['cid'];
1482 $values[$rid]['case'] = '<a href="' . CRM_Utils_System
::url('civicrm/case/ajax/details', sprintf('caseId=%d&cid=%d&snippet=4', $values[$rid]['case_id'], $values[$rid]['cid'])) . '" class="action-item crm-hover-button crm-summary-link"><i class="crm-i fa-folder-open-o"></i></a>';
1486 $values[$rid]['action'] = CRM_Core_Action
::formLink(
1492 'relationship.selector.row',
1495 unset($links[CRM_Core_Action
::MAX_ACTION
]);
1504 * Get get list of relationship type based on the target contact type.
1506 * @param string $targetContactType
1507 * It's valid contact tpye(may be Individual , Organization , Household).
1510 * array reference of all relationship types with context to current contact type .
1512 public static function getRelationType($targetContactType) {
1513 $relationshipType = [];
1514 $allRelationshipType = CRM_Core_PseudoConstant
::relationshipType();
1516 foreach ($allRelationshipType as $key => $type) {
1517 if ($type['contact_type_b'] == $targetContactType ||
empty($type['contact_type_b'])) {
1518 $relationshipType[$key . '_a_b'] = $type['label_a_b'];
1522 return $relationshipType;
1526 * Create / update / delete membership for related contacts.
1528 * This function will create/update/delete membership for related
1529 * contact based on 1) contact have active membership 2) that
1530 * membership is is extedned by the same relationship type to that
1531 * of the existing relationship.
1533 * @param int $contactId
1535 * @param array $params
1536 * array of values submitted by POST.
1539 * @param \const|int $action which action called this function
1541 * @param bool $active
1543 * @throws \CRM_Core_Exception
1544 * @throws \CiviCRM_API3_Exception
1546 public static function relatedMemberships($contactId, &$params, $ids, $action = CRM_Core_Action
::ADD
, $active = TRUE) {
1547 // Check the end date and set the status of the relationship
1549 $status = self
::CURRENT
;
1550 $targetContact = $targetContact = CRM_Utils_Array
::value('contact_check', $params, []);
1551 $today = date('Ymd');
1553 // If a relationship hasn't yet started, just return for now
1554 // TODO: handle edge-case of updating start_date of an existing relationship
1555 if (!empty($params['start_date'])) {
1556 $startDate = substr(CRM_Utils_Date
::format($params['start_date']), 0, 8);
1557 if ($today < $startDate) {
1562 if (!empty($params['end_date'])) {
1563 $endDate = substr(CRM_Utils_Date
::format($params['end_date']), 0, 8);
1564 if ($today > $endDate) {
1565 $status = self
::PAST
;
1569 if (($action & CRM_Core_Action
::ADD
) && ($status & self
::PAST
)) {
1570 // If relationship is PAST and action is ADD, do nothing.
1574 $rel = explode('_', $params['relationship_type_id']);
1576 $relTypeId = $rel[0];
1577 if (!empty($rel[1])) {
1578 $relDirection = "_{$rel[1]}_{$rel[2]}";
1581 // this call is coming from somewhere where the direction was resolved early on (e.g an api call)
1582 // so we can assume _a_b
1583 $relDirection = "_a_b";
1584 $targetContact = [$params['contact_id_b'] => 1];
1587 if (($action & CRM_Core_Action
::ADD
) ||
1588 ($action & CRM_Core_Action
::DELETE
)
1590 $contact = $contactId;
1592 elseif ($action & CRM_Core_Action
::UPDATE
) {
1593 $contact = $ids['contact'];
1594 $targetContact = [$ids['contactTarget'] => 1];
1597 // Build the 'values' array for
1600 // This will allow us to check if either of the contacts in
1601 // relationship have active memberships.
1606 $values[$contact] = [
1607 'relatedContacts' => $targetContact,
1608 'relationshipTypeId' => $relTypeId,
1609 'relationshipTypeDirection' => $relDirection,
1612 if (!empty($targetContact)) {
1613 foreach ($targetContact as $cid => $donCare) {
1615 'relatedContacts' => [$contact => 1],
1616 'relationshipTypeId' => $relTypeId,
1619 $relTypeParams = ['id' => $relTypeId];
1620 $relTypeValues = [];
1621 CRM_Contact_BAO_RelationshipType
::retrieve($relTypeParams, $relTypeValues);
1623 if (CRM_Utils_Array
::value('name_a_b', $relTypeValues) == CRM_Utils_Array
::value('name_b_a', $relTypeValues)) {
1624 $values[$cid]['relationshipTypeDirection'] = '_a_b';
1627 $values[$cid]['relationshipTypeDirection'] = ($relDirection == '_a_b') ?
'_b_a' : '_a_b';
1632 // CRM-15829 UPDATES
1633 // If we're looking for active memberships we must consider pending (id: 5) ones too.
1634 // Hence we can't just call CRM_Member_BAO_Membership::getValues below with the active flag, is it would completely miss pending relatioships.
1635 // As suggested by @davecivicrm, the pending status id is fetched using the CRM_Member_PseudoConstant::membershipStatus() class and method, since these ids differ from system to system.
1636 $pendingStatusId = array_search('Pending', CRM_Member_PseudoConstant
::membershipStatus());
1638 $query = 'SELECT * FROM `civicrm_membership_status`';
1640 $query .= ' WHERE `is_current_member` = 1 OR `id` = %1 ';
1643 $dao = CRM_Core_DAO
::executeQuery($query, [1 => [$pendingStatusId, 'Integer']]);
1645 while ($dao->fetch()) {
1646 $membershipStatusRecordIds[$dao->id
] = $dao->id
;
1649 // Now get the active memberships for all the contacts.
1650 // If contact have any valid membership(s), then add it to
1652 foreach ($values as $cid => $subValues) {
1653 $memParams = ['contact_id' => $cid];
1656 // CRM-15829 UPDATES
1657 // Since we want PENDING memberships as well, the $active flag needs to be set to false so that this will return all memberships and we can then filter the memberships based on the status IDs recieved above.
1658 CRM_Member_BAO_Membership
::getValues($memParams, $memberships, FALSE, TRUE);
1660 // CRM-15829 UPDATES
1661 // filter out the memberships returned by CRM_Member_BAO_Membership::getValues based on the status IDs fetched on line ~1462
1662 foreach ($memberships as $key => $membership) {
1664 if (!isset($memberships[$key]['status_id'])) {
1668 $membershipStatusId = $memberships[$key]['status_id'];
1669 if (!isset($membershipStatusRecordIds[$membershipStatusId])) {
1670 unset($memberships[$key]);
1674 if (empty($memberships)) {
1678 //get ownerMembershipIds for related Membership
1679 //this is to handle memberships being deleted and recreated
1680 if (!empty($memberships['owner_membership_ids'])) {
1681 $ownerMemIds[$cid] = $memberships['owner_membership_ids'];
1682 unset($memberships['owner_membership_ids']);
1685 $values[$cid]['memberships'] = $memberships;
1687 $deceasedStatusId = array_search('Deceased', CRM_Member_PseudoConstant
::membershipStatus());
1689 // done with 'values' array.
1690 // Finally add / edit / delete memberships for the related contacts
1692 foreach ($values as $cid => $details) {
1693 if (!array_key_exists('memberships', $details)) {
1697 $relatedContacts = array_keys(CRM_Utils_Array
::value('relatedContacts', $details, []));
1698 $mainRelatedContactId = reset($relatedContacts);
1700 foreach ($details['memberships'] as $membershipId => $membershipValues) {
1701 $membershipInherittedFromContactID = NULL;
1702 if (!empty($membershipValues['owner_membership_id'])) {
1703 // Use get not getsingle so that we get e-notice noise but not a fatal is the membership has already been deleted.
1704 $inheritedFromMembership = civicrm_api3('Membership', 'get', ['id' => $membershipValues['owner_membership_id'], 'sequential' => 1])['values'][0];
1705 $membershipInherittedFromContactID = (int) $inheritedFromMembership['contact_id'];
1708 if ($action & CRM_Core_Action
::DELETE
) {
1709 // @todo don't return relTypeId here - but it seems to be used later in a cryptic way (hint cryptic is not a complement).
1710 list($relTypeId, $isDeletable) = self
::isInheritedMembershipInvalidated($membershipValues, $values, $cid, $mainRelatedContactId);
1712 CRM_Member_BAO_Membership
::deleteRelatedMemberships($membershipValues['owner_membership_id'], $membershipValues['membership_contact_id']);
1716 if (($action & CRM_Core_Action
::UPDATE
) &&
1717 ($status & self
::PAST
) &&
1718 ($membershipValues['owner_membership_id'])
1720 // If relationship is PAST and action is UPDATE
1721 // then delete the RELATED membership
1722 CRM_Member_BAO_Membership
::deleteRelatedMemberships($membershipValues['owner_membership_id'],
1723 $membershipValues['membership_contact_id']
1728 // add / edit the memberships for related
1731 // Get the Membership Type Details.
1732 $membershipType = CRM_Member_BAO_MembershipType
::getMembershipTypeDetails($membershipValues['membership_type_id']);
1733 // Check if contact's relationship type exists in membership type
1735 if (!empty($membershipType['relationship_type_id'])) {
1736 $relTypeIds = explode(CRM_Core_DAO
::VALUE_SEPARATOR
, $membershipType['relationship_type_id']);
1738 if (!empty($membershipType['relationship_direction'])) {
1739 $relDirections = explode(CRM_Core_DAO
::VALUE_SEPARATOR
, $membershipType['relationship_direction']);
1741 foreach ($relTypeIds as $key => $value) {
1742 $relTypeDirs[] = $value . '_' . $relDirections[$key];
1744 $relTypeDir = $details['relationshipTypeId'] . $details['relationshipTypeDirection'];
1745 if (in_array($relTypeDir, $relTypeDirs)) {
1746 // Check if relationship being created/updated is
1747 // similar to that of membership type's
1750 $membershipValues['owner_membership_id'] = $membershipId;
1751 unset($membershipValues['id']);
1752 unset($membershipValues['membership_contact_id']);
1753 unset($membershipValues['contact_id']);
1754 unset($membershipValues['membership_id']);
1755 foreach ($details['relatedContacts'] as $relatedContactId => $donCare) {
1756 $membershipValues['contact_id'] = $relatedContactId;
1757 if ($deceasedStatusId &&
1758 CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $relatedContactId, 'is_deceased')
1760 $membershipValues['status_id'] = $deceasedStatusId;
1761 $membershipValues['skipStatusCal'] = TRUE;
1763 foreach (['join_date', 'start_date', 'end_date'] as $dateField) {
1764 if (!empty($membershipValues[$dateField])) {
1765 $membershipValues[$dateField] = CRM_Utils_Date
::processDate($membershipValues[$dateField]);
1769 if ($action & CRM_Core_Action
::UPDATE
) {
1770 //if updated relationship is already related to contact don't delete existing inherited membership
1771 if (in_array($relTypeId, $relTypeIds
1772 ) && !empty($values[$relatedContactId]['memberships']) && !empty($ownerMemIds
1773 ) && in_array($membershipValues['owner_membership_id'], $ownerMemIds[$relatedContactId])) {
1777 //delete the membership record for related
1778 //contact before creating new membership record.
1779 CRM_Member_BAO_Membership
::deleteRelatedMemberships($membershipId, $relatedContactId);
1781 //skip status calculation for pay later memberships.
1782 if (!empty($membershipValues['status_id']) && $membershipValues['status_id'] == $pendingStatusId) {
1783 $membershipValues['skipStatusCal'] = TRUE;
1785 // As long as the membership itself was not created by inheritance from the same contact
1786 // that stands to inherit the membership we add an inherited membership.
1787 if ($membershipInherittedFromContactID !== (int) $membershipValues['contact_id']) {
1788 $membershipValues = self
::addInheritedMembership($membershipValues);
1792 elseif ($action & CRM_Core_Action
::UPDATE
) {
1793 // if action is update and updated relationship do
1794 // not match with the existing
1795 // membership=>relationship then we need to
1796 // change the status of the membership record to expired for
1797 // previous relationship -- CRM-12078.
1798 // CRM-16087 we need to pass ownerMembershipId to isRelatedMembershipExpired function
1799 if (empty($params['relationship_ids']) && !empty($params['id'])) {
1800 $relIds = [$params['id']];
1803 $relIds = CRM_Utils_Array
::value('relationship_ids', $params);
1805 if (self
::isRelatedMembershipExpired($relTypeIds, $contactId, $mainRelatedContactId, $relTypeId,
1806 $relIds) && !empty($membershipValues['owner_membership_id']
1807 ) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) {
1808 $membershipValues['status_id'] = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_MembershipStatus', 'Expired', 'id', 'label');
1809 $type = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_MembershipType', $membershipValues['membership_type_id'], 'name', 'id');
1810 CRM_Member_BAO_Membership
::add($membershipValues);
1811 CRM_Core_Session
::setStatus(ts("Inherited membership %1 status was changed to Expired due to the change in relationship type.", [1 => $type]), ts('Record Updated'), 'alert');
1819 * Helper function to check whether the membership is expired or not.
1821 * Function takes a list of related membership types and if it is not also passed a
1822 * relationship ID of that types evaluates whether the membership status should be changed to expired.
1824 * @param array $membershipTypeRelationshipTypeIDs
1825 * Relation type IDs related to the given membership type.
1826 * @param int $contactId
1827 * @param int $mainRelatedContactId
1828 * @param int $relTypeId
1829 * @param array $relIds
1833 public static function isRelatedMembershipExpired($membershipTypeRelationshipTypeIDs, $contactId, $mainRelatedContactId, $relTypeId, $relIds) {
1834 if (empty($membershipTypeRelationshipTypeIDs) ||
in_array($relTypeId, $membershipTypeRelationshipTypeIDs)) {
1838 if (empty($relIds)) {
1843 1 => [$contactId, 'Integer'],
1844 2 => [$mainRelatedContactId, 'Integer'],
1847 if ($contactId == $mainRelatedContactId) {
1848 $recordsFound = (int) CRM_Core_DAO
::singleValueQuery("SELECT COUNT(*) FROM civicrm_relationship WHERE relationship_type_id IN ( " . implode(',', $membershipTypeRelationshipTypeIDs) . " ) AND
1849 contact_id_a IN ( %1 ) OR contact_id_b IN ( %1 ) AND id IN (" . implode(',', $relIds) . ")", $relParamas);
1850 if ($recordsFound) {
1856 $recordsFound = (int) CRM_Core_DAO
::singleValueQuery("SELECT COUNT(*) FROM civicrm_relationship WHERE relationship_type_id IN ( " . implode(',', $membershipTypeRelationshipTypeIDs) . " ) AND contact_id_a IN ( %1, %2 ) AND contact_id_b IN ( %1, %2 ) AND id NOT IN (" . implode(',', $relIds) . ")", $relParamas);
1858 if ($recordsFound) {
1866 * Get Current Employer for Contact.
1868 * @param $contactIds
1872 * array of the current employer
1874 public static function getCurrentEmployer($contactIds) {
1875 $contacts = implode(',', $contactIds);
1878 SELECT organization_name, id, employer_id
1879 FROM civicrm_contact
1880 WHERE id IN ( {$contacts} )
1883 $dao = CRM_Core_DAO
::executeQuery($query);
1884 $currentEmployer = [];
1885 while ($dao->fetch()) {
1886 $currentEmployer[$dao->id
]['org_id'] = $dao->employer_id
;
1887 $currentEmployer[$dao->id
]['org_name'] = $dao->organization_name
;
1890 return $currentEmployer;
1894 * Function to return list of permissioned contacts for a given contact and relationship type.
1896 * @param int $contactID
1897 * contact id whose permissioned contacts are to be found.
1898 * @param int $relTypeId
1899 * one or more relationship type id's.
1900 * @param string $name
1901 * @param string $contactType
1906 public static function getPermissionedContacts($contactID, $relTypeId = NULL, $name = NULL, $contactType = NULL) {
1908 $args = [1 => [$contactID, 'Integer']];
1909 $relationshipTypeClause = $contactTypeClause = '';
1912 // @todo relTypeId is only ever passed in as an int. Change this to reflect that -
1913 // probably being overly conservative by not doing so but working on stable release.
1914 $relationshipTypeClause = 'AND cr.relationship_type_id IN (%2) ';
1915 $args[2] = [$relTypeId, 'String'];
1919 $contactTypeClause = ' AND cr.relationship_type_id = crt.id AND crt.contact_type_b = %3 ';
1920 $args[3] = [$contactType, 'String'];
1924 SELECT cc.id as id, cc.sort_name as name
1925 FROM civicrm_relationship cr, civicrm_contact cc, civicrm_relationship_type crt
1927 cr.contact_id_a = %1 AND
1928 cr.is_permission_a_b = 1 AND
1929 IF(cr.end_date IS NULL, 1, (DATEDIFF( CURDATE( ), cr.end_date ) <= 0)) AND
1930 cr.is_active = 1 AND
1931 cc.id = cr.contact_id_b AND
1933 $relationshipTypeClause
1937 if (!empty($name)) {
1938 $name = CRM_Utils_Type
::escape($name, 'String');
1940 AND cc.sort_name LIKE '%$name%'";
1943 $dao = CRM_Core_DAO
::executeQuery($query, $args);
1944 while ($dao->fetch()) {
1945 $contacts[$dao->id
] = [
1946 'name' => $dao->name
,
1947 'value' => $dao->id
,
1955 * Merge relationships from otherContact to mainContact.
1957 * Called during contact merge operation
1959 * @param int $mainId
1960 * Contact id of main contact record.
1961 * @param int $otherId
1962 * Contact id of record which is going to merge.
1963 * @param array $sqls
1964 * (reference) array of sql statements to append to.
1966 * @see CRM_Dedupe_Merger::cpTables()
1968 public static function mergeRelationships($mainId, $otherId, &$sqls) {
1969 // Delete circular relationships
1970 $sqls[] = "DELETE FROM civicrm_relationship
1971 WHERE (contact_id_a = $mainId AND contact_id_b = $otherId)
1972 OR (contact_id_b = $mainId AND contact_id_a = $otherId)";
1974 // Delete relationship from other contact if main contact already has that relationship
1975 $sqls[] = "DELETE r2
1976 FROM civicrm_relationship r1, civicrm_relationship r2
1977 WHERE r1.relationship_type_id = r2.relationship_type_id
1980 r1.contact_id_a = $mainId AND r2.contact_id_a = $otherId AND r1.contact_id_b = r2.contact_id_b
1981 OR r1.contact_id_b = $mainId AND r2.contact_id_b = $otherId AND r1.contact_id_a = r2.contact_id_a
1983 (r1.contact_id_a = $mainId AND r2.contact_id_b = $otherId AND r1.contact_id_b = r2.contact_id_a
1984 OR r1.contact_id_b = $mainId AND r2.contact_id_a = $otherId AND r1.contact_id_a = r2.contact_id_b)
1985 AND r1.relationship_type_id IN (SELECT id FROM civicrm_relationship_type WHERE name_b_a = name_a_b)
1989 // Move relationships
1990 $sqls[] = "UPDATE IGNORE civicrm_relationship SET contact_id_a = $mainId WHERE contact_id_a = $otherId";
1991 $sqls[] = "UPDATE IGNORE civicrm_relationship SET contact_id_b = $mainId WHERE contact_id_b = $otherId";
1993 // Move current employer id (name will get updated later)
1994 $sqls[] = "UPDATE civicrm_contact SET employer_id = $mainId WHERE employer_id = $otherId";
1998 * Set 'is_valid' field to false for all relationships whose end date is in the past, ie. are expired.
2001 * True on success, false if error is encountered.
2002 * @throws \CiviCRM_API3_Exception
2004 public static function disableExpiredRelationships() {
2005 $query = "SELECT id FROM civicrm_relationship WHERE is_active = 1 AND end_date < CURDATE()";
2007 $dao = CRM_Core_DAO
::executeQuery($query);
2008 while ($dao->fetch()) {
2009 $result = CRM_Contact_BAO_Relationship
::setIsActive($dao->id
, FALSE);
2010 // Result will be NULL if error occurred. We abort early if error detected.
2011 if ($result == NULL) {
2019 * Function filters the query by possible relationships for the membership type.
2021 * It is intended to be called when constructing queries for the api (reciprocal & non-reciprocal)
2022 * and to add clauses to limit the return to those relationships which COULD inherit a membership type
2023 * (as opposed to those who inherit a particular membership
2025 * @param array $params
2027 * @param null $direction
2029 * @return array|void
2030 * @throws \CiviCRM_API3_Exception
2032 public static function membershipTypeToRelationshipTypes(&$params, $direction = NULL) {
2033 $membershipType = civicrm_api3('membership_type', 'getsingle', [
2034 'id' => $params['membership_type_id'],
2035 'return' => 'relationship_type_id, relationship_direction',
2037 $relationshipTypes = $membershipType['relationship_type_id'];
2038 if (empty($relationshipTypes)) {
2041 // if we don't have any contact data we can only filter on type
2042 if (empty($params['contact_id']) && empty($params['contact_id_a']) && empty($params['contact_id_a'])) {
2043 $params['relationship_type_id'] = ['IN' => $relationshipTypes];
2047 $relationshipDirections = (array) $membershipType['relationship_direction'];
2048 // if we have contact_id_a OR contact_id_b we can make a call here
2049 // if we have contact??
2050 foreach ($relationshipDirections as $index => $mtdirection) {
2051 if (isset($params['contact_id_a']) && $mtdirection == 'a_b' ||
$direction == 'a_b') {
2052 $types[] = $relationshipTypes[$index];
2054 if (isset($params['contact_id_b']) && $mtdirection == 'b_a' ||
$direction == 'b_a') {
2055 $types[] = $relationshipTypes[$index];
2058 if (!empty($types)) {
2059 $params['relationship_type_id'] = ['IN' => $types];
2061 elseif (!empty($clauses)) {
2062 return explode(' OR ', $clauses);
2065 // effectively setting it to return no results
2066 $params['relationship_type_id'] = 0;
2072 * Wrapper for contact relationship selector.
2074 * @param array $params
2075 * Associated array for params record id.
2078 * associated array of contact relationships
2079 * @throws \Exception
2081 public static function getContactRelationshipSelector(&$params) {
2082 // format the params
2083 $params['offset'] = ($params['page'] - 1) * $params['rp'];
2084 $params['sort'] = CRM_Utils_Array
::value('sortBy', $params);
2086 if ($params['context'] == 'past') {
2087 $relationshipStatus = CRM_Contact_BAO_Relationship
::INACTIVE
;
2089 elseif ($params['context'] == 'all') {
2090 $relationshipStatus = CRM_Contact_BAO_Relationship
::ALL
;
2093 $relationshipStatus = CRM_Contact_BAO_Relationship
::CURRENT
;
2096 // check logged in user for permission
2097 $page = new CRM_Core_Page();
2098 CRM_Contact_Page_View
::checkUserPermission($page, $params['contact_id']);
2099 $permissions = [$page->_permission
];
2100 if ($page->_permission
== CRM_Core_Permission
::EDIT
) {
2101 $permissions[] = CRM_Core_Permission
::DELETE
;
2103 $mask = CRM_Core_Action
::mask($permissions);
2105 $permissionedContacts = TRUE;
2106 if ($params['context'] != 'user') {
2107 $links = CRM_Contact_Page_View_Relationship
::links();
2110 $links = CRM_Contact_Page_View_UserDashBoard
::links();
2113 // get contact relationships
2114 $relationships = CRM_Contact_BAO_Relationship
::getRelationship($params['contact_id'],
2115 $relationshipStatus,
2116 $params['rp'], 0, 0,
2118 $permissionedContacts,
2122 $contactRelationships = [];
2123 $params['total'] = $relationships['total_relationships'];
2124 unset($relationships['total_relationships']);
2125 if (!empty($relationships)) {
2127 $displayName = CRM_Contact_BAO_Contact
::displayName($params['contact_id']);
2130 foreach ($relationships as $relationshipId => $values) {
2133 $relationship['DT_RowId'] = $values['id'];
2134 $relationship['DT_RowClass'] = 'crm-entity';
2135 if ($values['is_active'] == 0) {
2136 $relationship['DT_RowClass'] .= ' disabled';
2139 $relationship['DT_RowAttr'] = [];
2140 $relationship['DT_RowAttr']['data-entity'] = 'relationship';
2141 $relationship['DT_RowAttr']['data-id'] = $values['id'];
2143 //Add image icon for related contacts: CRM-14919; CRM-19668
2144 $contactType = (!empty($values['contact_sub_type'])) ?
$values['contact_sub_type'] : $values['contact_type'];
2145 $icon = CRM_Contact_BAO_Contact_Utils
::getImage($contactType,
2149 $relationship['sort_name'] = $icon . ' ' . CRM_Utils_System
::href(
2151 'civicrm/contact/view',
2152 "reset=1&cid={$values['cid']}");
2154 $relationship['relation'] = CRM_Utils_Array
::value('case', $values, '') . CRM_Utils_System
::href(
2155 $values['relation'],
2156 'civicrm/contact/view/rel',
2157 "action=view&reset=1&cid={$values['cid']}&id={$values['id']}&rtype={$values['rtype']}");
2159 if (!empty($values['description'])) {
2160 $relationship['relation'] .= "<p class='description'>{$values['description']}</p>";
2163 if ($params['context'] == 'current') {
2164 $smarty = CRM_Core_Smarty
::singleton();
2168 'permContact' => $params['contact_id'],
2169 'permDisplayName' => $displayName,
2170 'otherContact' => $values['cid'],
2171 'otherDisplayName' => $values['display_name'],
2172 'columnKey' => 'sort_name',
2175 'permContact' => $values['cid'],
2176 'permDisplayName' => $values['display_name'],
2177 'otherContact' => $params['contact_id'],
2178 'otherDisplayName' => $displayName,
2179 'columnKey' => 'relation',
2183 foreach ($contactCombos as $combo) {
2184 foreach ([CRM_Contact_BAO_Relationship
::EDIT
, CRM_Contact_BAO_Relationship
::VIEW
] as $permType) {
2185 $smarty->assign('permType', $permType);
2186 if (($combo['permContact'] == $values['contact_id_a'] and $values['is_permission_a_b'] == $permType)
2187 ||
($combo['permContact'] == $values['contact_id_b'] and $values['is_permission_b_a'] == $permType)
2189 $smarty->assign('permDisplayName', $combo['permDisplayName']);
2190 $smarty->assign('otherDisplayName', $combo['otherDisplayName']);
2191 $relationship[$combo['columnKey']] .= $smarty->fetch('CRM/Contact/Page/View/RelationshipPerm.tpl');
2197 $relationship['start_date'] = CRM_Utils_Date
::customFormat($values['start_date']);
2198 $relationship['end_date'] = CRM_Utils_Date
::customFormat($values['end_date']);
2199 $relationship['city'] = $values['city'];
2200 $relationship['state'] = $values['state'];
2201 $relationship['email'] = $values['email'];
2202 $relationship['phone'] = $values['phone'];
2203 $relationship['links'] = $values['action'];
2205 array_push($contactRelationships, $relationship);
2209 $columnHeaders = self
::getColumnHeaders();
2211 CRM_Utils_Hook
::searchColumns('relationship.rows', $columnHeaders, $contactRelationships, $selector);
2213 $relationshipsDT = [];
2214 $relationshipsDT['data'] = $contactRelationships;
2215 $relationshipsDT['recordsTotal'] = $params['total'];
2216 $relationshipsDT['recordsFiltered'] = $params['total'];
2218 return $relationshipsDT;
2224 public static function getColumnHeaders() {
2227 'name' => ts('Relationship'),
2228 'sort' => 'relation',
2229 'direction' => CRM_Utils_Sort
::ASCENDING
,
2233 'sort' => 'sort_name',
2234 'direction' => CRM_Utils_Sort
::ASCENDING
,
2237 'name' => ts('Start'),
2238 'sort' => 'start_date',
2239 'direction' => CRM_Utils_Sort
::DONTCARE
,
2242 'name' => ts('End'),
2243 'sort' => 'end_date',
2244 'direction' => CRM_Utils_Sort
::DONTCARE
,
2247 'name' => ts('City'),
2249 'direction' => CRM_Utils_Sort
::DONTCARE
,
2252 'name' => ts('State/Prov'),
2254 'direction' => CRM_Utils_Sort
::DONTCARE
,
2257 'name' => ts('Email'),
2259 'direction' => CRM_Utils_Sort
::DONTCARE
,
2262 'name' => ts('Phone'),
2264 'direction' => CRM_Utils_Sort
::DONTCARE
,
2269 'direction' => CRM_Utils_Sort
::DONTCARE
,
2277 public static function buildOptions($fieldName, $context = NULL, $props = []) {
2278 if ($fieldName === 'relationship_type_id') {
2279 return self
::buildRelationshipTypeOptions($props);
2282 return parent
::buildOptions($fieldName, $context, $props);
2286 * Builds a list of options available for relationship types
2288 * @param array $params
2289 * - contact_type: Limits by contact type on the "A" side
2290 * - relationship_id: Used to find the value for contact type for "B" side.
2291 * If contact_a matches provided contact_id then type of contact_b will
2292 * be used. Otherwise uses type of contact_a. Must be used with contact_id
2293 * - contact_id: Limits by contact types of this contact on the "A" side
2294 * - is_form: Returns array with keys indexed for use in a quickform
2295 * - relationship_direction: For relationship types with duplicate names
2296 * on both sides, defines which option should be returned, a_b or b_a
2300 public static function buildRelationshipTypeOptions($params = []) {
2301 $contactId = CRM_Utils_Array
::value('contact_id', $params);
2302 $direction = CRM_Utils_Array
::value('relationship_direction', $params, 'a_b');
2303 $relationshipId = CRM_Utils_Array
::value('relationship_id', $params);
2304 $contactType = CRM_Utils_Array
::value('contact_type', $params);
2305 $isForm = CRM_Utils_Array
::value('is_form', $params);
2308 // getContactRelationshipType will return an empty set if these are not set
2309 if (!$contactId && !$relationshipId && !$contactType) {
2313 $labels = self
::getContactRelationshipType(
2326 $names = self
::getContactRelationshipType(
2335 // ensure $names contains only entries in $labels
2336 $names = array_intersect_key($names, $labels);
2338 $nameToLabels = array_combine($names, $labels);
2340 return $nameToLabels;
2344 * Process the params from api, form and check if current
2345 * employer should be set or unset.
2347 * @param array $params
2348 * @param int $relationshipId
2349 * @param int|null $updatedRelTypeID
2352 * TRUE if current employer needs to be cleared.
2353 * @throws \CiviCRM_API3_Exception
2355 public static function isCurrentEmployerNeedingToBeCleared($params, $relationshipId, $updatedRelTypeID = NULL) {
2356 $existingTypeID = (int) CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Relationship', $relationshipId, 'relationship_type_id');
2357 $updatedRelTypeID = $updatedRelTypeID ?
$updatedRelTypeID : $existingTypeID;
2358 $currentEmployerID = (int) civicrm_api3('Contact', 'getvalue', ['return' => 'current_employer_id', 'id' => $params['contact_id_a']]);
2360 if ($currentEmployerID !== (int) $params['contact_id_b'] ||
!self
::isRelationshipTypeCurrentEmployer($existingTypeID)) {
2363 //Clear employer if relationship is expired.
2364 if (!empty($params['end_date']) && strtotime($params['end_date']) < time()) {
2367 //current employer checkbox is disabled on the form.
2368 //inactive or relationship type(employer of) is updated.
2369 if ((isset($params['is_current_employer']) && empty($params['is_current_employer']))
2370 ||
((isset($params['is_active']) && empty($params['is_active'])))
2371 ||
$existingTypeID != $updatedRelTypeID) {
2372 // If there are no other active employer relationships between the same 2 contacts...
2373 if (!civicrm_api3('Relationship', 'getcount', [
2375 'relationship_type_id' => $existingTypeID,
2376 'id' => ['<>' => $params['id']],
2377 'contact_id_a' => $params['contact_id_a'],
2378 'contact_id_b' => $params['contact_id_b'],
2388 * Is this a current employer relationship type.
2390 * @todo - this could use cached pseudoconstant lookups.
2392 * @param int $existingTypeID
2396 private static function isRelationshipTypeCurrentEmployer(int $existingTypeID): bool {
2397 $isCurrentEmployerRelationshipType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType', $existingTypeID, 'name_b_a') === 'Employer of';
2398 return $isCurrentEmployerRelationshipType;
2402 * Is the inherited relationship invalidated by this relationship change.
2404 * @param $membershipValues
2405 * @param array $values
2407 * @param int $mainRelatedContactId
2410 * @throws \CiviCRM_API3_Exception
2412 private static function isInheritedMembershipInvalidated($membershipValues, array $values, $cid, $mainRelatedContactId): array {
2413 $membershipType = CRM_Member_BAO_MembershipType
::getMembershipType($membershipValues['membership_type_id']);
2414 $relTypeIds = $membershipType['relationship_type_id'];
2415 $membshipInheritedFrom = $membershipValues['owner_membership_id'] ??
NULL;
2416 if (!$membshipInheritedFrom ||
!in_array($values[$cid]['relationshipTypeId'], $relTypeIds)) {
2417 return [implode(',', $relTypeIds), FALSE];
2419 //CRM-16300 check if owner membership exist for related membership
2420 $isDeletable = !empty($values[$mainRelatedContactId]['memberships'][$membshipInheritedFrom]);
2421 return [implode(',', $relTypeIds), $isDeletable];
2425 * Add an inherited membership, provided max related not exceeded.
2427 * @param array $membershipValues
2430 * @throws \CRM_Core_Exception
2432 protected static function addInheritedMembership($membershipValues) {
2435 FROM civicrm_membership
2436 LEFT JOIN civicrm_membership_status ON (civicrm_membership_status.id = civicrm_membership.status_id)
2437 WHERE membership_type_id = {$membershipValues['membership_type_id']}
2438 AND owner_membership_id = {$membershipValues['owner_membership_id']}
2439 AND is_current_member = 1";
2440 $result = CRM_Core_DAO
::singleValueQuery($query);
2441 if ($result < CRM_Utils_Array
::value('max_related', $membershipValues, PHP_INT_MAX
)) {
2442 CRM_Member_BAO_Membership
::create($membershipValues);
2444 return $membershipValues;