3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
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 * Create function - use the API instead.
43 * Note that the previous create function has been renamed 'legacyCreateMultiple'
44 * and this is new in 4.6
45 * All existing calls have been changed to legacyCreateMultiple except the api call - however, it is recommended
46 * that you call that as the end to end testing here is based on the api & refactoring may still be done.
48 * @param array $params
50 * @return \CRM_Contact_BAO_Relationship
51 * @throws \CRM_Core_Exception
53 public static function create(&$params) {
55 $extendedParams = self
::loadExistingRelationshipDetails($params);
56 // When id is specified we always wan't to update, so we don't need to
57 // check for duplicate relations.
58 if (!isset($params['id']) && self
::checkDuplicateRelationship($extendedParams, $extendedParams['contact_id_a'], $extendedParams['contact_id_b'], CRM_Utils_Array
::value('id', $extendedParams, 0))) {
59 throw new CRM_Core_Exception('Duplicate Relationship');
61 $params = $extendedParams;
62 if (self
::checkValidRelationship($params, $params, 0)) {
63 throw new CRM_Core_Exception('Invalid Relationship');
65 $relationship = self
::add($params);
66 if (!empty($params['contact_id_a'])) {
68 'contactTarget' => $relationship->contact_id_b
,
69 'contact' => $params['contact_id_a'],
72 //CRM-16087 removed additional call to function relatedMemberships which is already called by disableEnableRelationship
73 //resulting in membership being created twice
74 if (array_key_exists('is_active', $params) && empty($params['is_active'])) {
75 $action = CRM_Core_Action
::DISABLE
;
79 $action = CRM_Core_Action
::ENABLE
;
82 $id = empty($params['id']) ?
$relationship->id
: $params['id'];
83 self
::disableEnableRelationship($id, $action, $params, $ids, $active);
86 if (empty($params['skipRecentView'])) {
87 self
::addRecent($params, $relationship);
94 * Create multiple relationships for one contact.
96 * The relationship details are the same for each relationship except the secondary contact
99 * @param array $params
100 * Parameters for creating multiple relationships.
101 * The parameters are the same as for relationship create function except that the non-primary
102 * end of the relationship should be an array of one or more contact IDs.
103 * @param string $primaryContactLetter
104 * a or b to denote the primary contact for this action. The secondary may be multiple contacts
105 * and should be an array.
108 * @throws \CRM_Core_Exception
110 public static function createMultiple($params, $primaryContactLetter) {
111 $secondaryContactLetter = ($primaryContactLetter == 'a') ?
'b' : 'a';
112 $secondaryContactIDs = $params['contact_id_' . $secondaryContactLetter];
113 $valid = $invalid = $duplicate = $saved = 0;
114 $relationshipIDs = array();
115 foreach ($secondaryContactIDs as $secondaryContactID) {
117 $params['contact_id_' . $secondaryContactLetter] = $secondaryContactID;
118 $relationship = civicrm_api3('relationship', 'create', $params);
119 $relationshipIDs[] = $relationship['id'];
122 catch (CiviCRM_API3_Exception
$e) {
123 switch ($e->getMessage()) {
124 case 'Duplicate Relationship':
128 case 'Invalid Relationship':
133 throw new CRM_Core_Exception('unknown relationship create error ' . $e->getMessage());
140 'invalid' => $invalid,
141 'duplicate' => $duplicate,
143 'relationship_ids' => $relationshipIDs,
148 * Takes an associative array and creates a relationship object.
149 * @deprecated For single creates use the api instead (it's tested).
150 * For multiple a new variant of this function needs to be written and migrated to as this is a bit
153 * @param array $params
154 * (reference ) an assoc array of name/value pairs.
156 * The array that holds all the db ids.
157 * per http://wiki.civicrm.org/confluence/display/CRM/Database+layer
158 * "we are moving away from the $ids param "
160 * @return CRM_Contact_BAO_Relationship
162 public static function legacyCreateMultiple(&$params, $ids = array()) {
163 $valid = $invalid = $duplicate = $saved = 0;
164 $relationships = $relationshipIds = array();
165 $relationshipId = CRM_Utils_Array
::value('relationship', $ids, CRM_Utils_Array
::value('id', $params));
167 //CRM-9015 - the hooks are called here & in add (since add doesn't call create)
168 // but in future should be tidied per ticket
169 if (empty($relationshipId)) {
176 CRM_Utils_Hook
::pre($hook, 'Relationship', $relationshipId, $params);
178 if (!$relationshipId) {
179 // creating a new relationship
180 $dataExists = self
::dataExists($params);
182 return array(FALSE, TRUE, FALSE, FALSE, NULL);
184 $relationshipIds = array();
185 foreach ($params['contact_check'] as $key => $value) {
186 // check if the relationship is valid between contacts.
187 // step 1: check if the relationship is valid if not valid skip and keep the count
188 // step 2: check the if two contacts already have a relationship if yes skip and keep the count
189 // step 3: if valid relationship then add the relation and keep the count
192 $contactFields = self
::setContactABFromIDs($params, $ids, $key);
193 $errors = self
::checkValidRelationship($contactFields, $ids, $key);
199 //CRM-16978:check duplicate relationship as per case id.
200 if ($caseId = CRM_Utils_Array
::value('case_id', $params)) {
201 $contactFields['case_id'] = $caseId;
204 self
::checkDuplicateRelationship(
206 CRM_Utils_Array
::value('contact', $ids),
215 $singleInstanceParams = array_merge($params, $contactFields);
216 $relationship = self
::add($singleInstanceParams);
217 $relationshipIds[] = $relationship->id
;
218 $relationships[$relationship->id
] = $relationship;
221 // editing the relationship
224 // check for duplicate relationship
225 // @todo this code doesn't cope well with updates - causes e-Notices.
226 // API has a lot of code to work around
227 // this but should review this code & remove the extra handling from the api
228 // it seems doubtful any of this is relevant if the contact fields & relationship
229 // type fields are not set
231 self
::checkDuplicateRelationship(
233 CRM_Utils_Array
::value('contact', $ids),
234 $ids['contactTarget'],
239 return array($valid, $invalid, $duplicate, $saved, NULL);
242 $validContacts = TRUE;
243 //validate contacts in update mode also.
244 $contactFields = self
::setContactABFromIDs($params, $ids, $ids['contactTarget']);
245 if (!empty($ids['contact']) && !empty($ids['contactTarget'])) {
246 if (self
::checkValidRelationship($contactFields, $ids, $ids['contactTarget'])) {
247 $validContacts = FALSE;
251 if ($validContacts) {
252 // editing an existing relationship
253 $singleInstanceParams = array_merge($params, $contactFields);
254 $relationship = self
::add($singleInstanceParams, $ids, $ids['contactTarget']);
255 $relationshipIds[] = $relationship->id
;
256 $relationships[$relationship->id
] = $relationship;
261 // do not add to recent items for import, CRM-4399
262 if (!(!empty($params['skipRecentView']) ||
$invalid ||
$duplicate)) {
263 self
::addRecent($params, $relationship);
266 return array($valid, $invalid, $duplicate, $saved, $relationshipIds, $relationships);
270 * This is the function that check/add if the relationship created is valid.
272 * @param array $params
273 * (reference ) an assoc array of name/value pairs.
275 * The array that holds all the db ids.
276 * @param int $contactId
277 * This is contact id for adding relationship.
279 * @return CRM_Contact_BAO_Relationship
281 public static function add(&$params, $ids = array(), $contactId = NULL) {
282 $relationshipId = CRM_Utils_Array
::value('relationship', $ids, CRM_Utils_Array
::value('id', $params));
285 if ($relationshipId) {
288 //@todo hook are called from create and add - remove one
289 CRM_Utils_Hook
::pre($hook, 'Relationship', $relationshipId, $params);
291 $relationshipTypes = CRM_Utils_Array
::value('relationship_type_id', $params);
293 // explode the string with _ to get the relationship type id
294 // and to know which contact has to be inserted in
295 // contact_id_a and which one in contact_id_b
296 list($type) = explode('_', $relationshipTypes);
298 // check if the relationship type is Head of Household then update the
299 // household's primary contact with this contact.
301 CRM_Contact_BAO_Household
::updatePrimaryContact($params['contact_id_b'], $params['contact_id_a']);
304 $relationship = new CRM_Contact_BAO_Relationship();
305 //@todo this code needs to be updated for the possibility that not all fields are set
306 // by using $relationship->copyValues($params);
308 $relationship->contact_id_b
= $params['contact_id_b'];
309 $relationship->contact_id_a
= $params['contact_id_a'];
310 $relationship->relationship_type_id
= $type;
311 $relationship->id
= $relationshipId;
313 $dateFields = array('end_date', 'start_date');
315 foreach (self
::getdefaults() as $defaultField => $defaultValue) {
316 if (isset($params[$defaultField])) {
317 if (in_array($defaultField, $dateFields)) {
318 $relationship->$defaultField = CRM_Utils_Date
::format(CRM_Utils_Array
::value($defaultField, $params));
319 if (!$relationship->$defaultField) {
320 $relationship->$defaultField = 'NULL';
324 $relationship->$defaultField = $params[$defaultField];
327 elseif (!$relationshipId) {
328 $relationship->$defaultField = $defaultValue;
332 $relationship->save();
334 // add custom field values
335 if (!empty($params['custom'])) {
336 CRM_Core_BAO_CustomValueTable
::store($params['custom'], 'civicrm_relationship', $relationship->id
);
339 $relationship->free();
341 CRM_Utils_Hook
::post($hook, 'Relationship', $relationship->id
, $relationship);
343 return $relationship;
347 * Add relationship to recent links.
349 * @param array $params
350 * @param CRM_Contact_DAO_Relationship $relationship
352 public static function addRecent($params, $relationship) {
353 $url = CRM_Utils_System
::url('civicrm/contact/view/rel',
354 "action=view&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&context=home"
356 $session = CRM_Core_Session
::singleton();
357 $recentOther = array();
358 if (($session->get('userID') == $relationship->contact_id_a
) ||
359 CRM_Contact_BAO_Contact_Permission
::allow($relationship->contact_id_a
, CRM_Core_Permission
::EDIT
)
361 $rType = substr(CRM_Utils_Array
::value('relationship_type_id', $params), -3);
362 $recentOther = array(
363 'editUrl' => CRM_Utils_System
::url('civicrm/contact/view/rel',
364 "action=update&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home"
366 'deleteUrl' => CRM_Utils_System
::url('civicrm/contact/view/rel',
367 "action=delete&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home"
371 $title = CRM_Contact_BAO_Contact
::displayName($relationship->contact_id_a
) . ' (' . CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType',
372 $relationship->relationship_type_id
, 'label_a_b'
373 ) . ' ' . CRM_Contact_BAO_Contact
::displayName($relationship->contact_id_b
) . ')';
375 CRM_Utils_Recent
::add($title,
379 $relationship->contact_id_a
,
386 * Load contact ids and relationship type id when doing a create call if not provided.
388 * There are are various checks done in create which require this information which is optional
391 * @param array $params
392 * Parameters passed to create call.
395 * Parameters with missing fields added if required.
397 public static function loadExistingRelationshipDetails($params) {
398 if (!empty($params['contact_id_a'])
399 && !empty($params['contact_id_b'])
400 && is_numeric($params['relationship_type_id'])) {
403 if (empty($params['id'])) {
407 $fieldsToFill = array('contact_id_a', 'contact_id_b', 'relationship_type_id');
408 $result = CRM_Core_DAO
::executeQuery("SELECT " . implode(',', $fieldsToFill) . " FROM civicrm_relationship WHERE id = %1", array(
414 while ($result->fetch()) {
415 foreach ($fieldsToFill as $field) {
416 $params[$field] = !empty($params[$field]) ?
$params[$field] : $result->$field;
423 * Resolve passed in contact IDs to contact_id_a & contact_id_b.
425 * @param array $params
427 * @param null $contactID
430 * @throws \CRM_Core_Exception
432 public static function setContactABFromIDs($params, $ids = array(), $contactID = NULL) {
433 $returnFields = array();
435 // $ids['contact'] is deprecated but comes from legacyCreateMultiple function.
436 if (empty($ids['contact'])) {
437 if (!empty($params['id'])) {
438 return self
::loadExistingRelationshipDetails($params);
440 throw new CRM_Core_Exception('Cannot create relationship, insufficient contact IDs provided');
442 if (isset($params['relationship_type_id']) && !is_numeric($params['relationship_type_id'])) {
443 $relationshipTypes = CRM_Utils_Array
::value('relationship_type_id', $params);
444 list($relationshipTypeID, $first) = explode('_', $relationshipTypes);
445 $returnFields['relationship_type_id'] = $relationshipTypeID;
447 foreach (array('a', 'b') as $contactLetter) {
448 if (empty($params['contact_' . $contactLetter])) {
449 if ($first == $contactLetter) {
450 $returnFields['contact_id_' . $contactLetter] = CRM_Utils_Array
::value('contact', $ids);
453 $returnFields['contact_id_' . $contactLetter] = $contactID;
459 return $returnFields;
463 * Specify defaults for creating a relationship.
466 * array of defaults for creating relationship
468 public static function getdefaults() {
471 'is_permission_a_b' => 0,
472 'is_permission_b_a' => 0,
474 'start_date' => 'NULL',
476 'end_date' => 'NULL',
482 * Check if there is data to create the object.
484 * @param array $params
485 * (reference ) an assoc array of name/value pairs.
489 public static function dataExists(&$params) {
490 // return if no data present
491 if (!is_array(CRM_Utils_Array
::value('contact_check', $params))) {
498 * Get get list of relationship type based on the contact type.
500 * @param int $contactId
501 * This is the contact id of the current contact.
502 * @param null $contactSuffix
503 * @param string $relationshipId
504 * The id of the existing relationship if any.
505 * @param string $contactType
508 * If true returns relationship types in both the direction.
509 * @param string $column
510 * Name/label that going to retrieve from db.
511 * @param bool $biDirectional
512 * @param array $contactSubType
513 * Includes relationship types between this subtype.
514 * @param bool $onlySubTypeRelationTypes
515 * If set only subtype which is passed by $contactSubType
516 * related relationship types get return
519 * array reference of all relationship types with context to current contact.
521 public static function getContactRelationshipType(
523 $contactSuffix = NULL,
524 $relationshipId = NULL,
528 $biDirectional = TRUE,
529 $contactSubType = NULL,
530 $onlySubTypeRelationTypes = FALSE
533 $relationshipType = array();
534 $allRelationshipType = CRM_Core_PseudoConstant
::relationshipType($column);
536 $otherContactType = NULL;
537 if ($relationshipId) {
538 $relationship = new CRM_Contact_DAO_Relationship();
539 $relationship->id
= $relationshipId;
540 if ($relationship->find(TRUE)) {
541 $contact = new CRM_Contact_DAO_Contact();
542 $contact->id
= ($relationship->contact_id_a
=== $contactId) ?
$relationship->contact_id_b
: $relationship->contact_id_a
;
544 if ($contact->find(TRUE)) {
545 $otherContactType = $contact->contact_type
;
546 //CRM-5125 for contact subtype specific relationshiptypes
547 if ($contact->contact_sub_type
) {
548 $otherContactSubType = $contact->contact_sub_type
;
554 if (empty($contactSubType)) {
555 $contactSubType = array();
558 $contactType = CRM_Contact_BAO_Contact
::getContactType($contactId);
559 $contactSubType = CRM_Contact_BAO_Contact
::getContactSubType($contactId);
562 foreach ($allRelationshipType as $key => $value) {
563 // the contact type is required or matches
564 if (((!$value['contact_type_a']) ||
565 $value['contact_type_a'] == $contactType
567 // the other contact type is required or present or matches
568 ((!$value['contact_type_b']) ||
569 (!$otherContactType) ||
570 $value['contact_type_b'] == $otherContactType
572 (in_array($value['contact_sub_type_a'], $contactSubType) ||
573 (!$value['contact_sub_type_a'] && !$onlySubTypeRelationTypes)
576 $relationshipType[$key . '_a_b'] = $value["{$column}_a_b"];
579 if (((!$value['contact_type_b']) ||
580 $value['contact_type_b'] == $contactType
582 ((!$value['contact_type_a']) ||
583 (!$otherContactType) ||
584 $value['contact_type_a'] == $otherContactType
586 (in_array($value['contact_sub_type_b'], $contactSubType) ||
587 (!$value['contact_sub_type_b'] && !$onlySubTypeRelationTypes)
590 $relationshipType[$key . '_b_a'] = $value["{$column}_b_a"];
594 $relationshipType[$key . '_a_b'] = $value["{$column}_a_b"];
595 $relationshipType[$key . '_b_a'] = $value["{$column}_b_a"];
599 if ($biDirectional) {
600 $relationshipType = self
::removeRelationshipTypeDuplicates($relationshipType, $contactSuffix);
603 // sort the relationshipType in ascending order CRM-7736
604 asort($relationshipType);
605 return $relationshipType;
609 * Given a list of relationship types, return the list with duplicate types
610 * removed, being careful to retain only the duplicate which matches the given
611 * 'a_b' or 'b_a' suffix.
613 * @param array $relationshipTypeList A list of relationship types, in the format
614 * returned by self::getContactRelationshipType().
615 * @param string $suffix Either 'a_b' or 'b_a'; defaults to 'a_b'
617 * @return array The modified value of $relationshipType
619 public static function removeRelationshipTypeDuplicates($relationshipTypeList, $suffix = NULL) {
620 if (empty($suffix)) {
624 // Find those labels which are listed more than once.
625 $duplicateValues = array_diff_assoc($relationshipTypeList, array_unique($relationshipTypeList));
627 // For each duplicate label, find its keys, and remove from $relationshipType
628 // the key which does not match $suffix.
629 foreach ($duplicateValues as $value) {
630 $keys = array_keys($relationshipTypeList, $value);
631 foreach ($keys as $key) {
632 if (substr($key, -3) != $suffix) {
633 unset($relationshipTypeList[$key]);
637 return $relationshipTypeList;
641 * Delete current employer relationship.
646 * @return CRM_Contact_DAO_Relationship
648 public static function clearCurrentEmployer($id, $action) {
649 $relationship = new CRM_Contact_DAO_Relationship();
650 $relationship->id
= $id;
651 $relationship->find(TRUE);
653 //to delete relationship between household and individual \
654 //or between individual and organization
655 if (($action & CRM_Core_Action
::DISABLE
) ||
($action & CRM_Core_Action
::DELETE
)) {
656 $relTypes = CRM_Utils_Array
::index(array('name_a_b'), CRM_Core_PseudoConstant
::relationshipType('name'));
658 (isset($relTypes['Employee of']) && $relationship->relationship_type_id
== $relTypes['Employee of']['id']) ||
659 (isset ($relTypes['Household Member of']) && $relationship->relationship_type_id
== $relTypes['Household Member of']['id'])
661 $sharedContact = new CRM_Contact_DAO_Contact();
662 $sharedContact->id
= $relationship->contact_id_a
;
663 $sharedContact->find(TRUE);
666 // changed FROM "...relationship->relationship_type_id == 4..." TO "...relationship->relationship_type_id == 5..."
667 // As the system should be looking for type "employer of" (id 5) and not "sibling of" (id 4)
668 // 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.
669 $employerRelTypeId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
671 if ($relationship->relationship_type_id
== $employerRelTypeId && $relationship->contact_id_b
== $sharedContact->employer_id
) {
672 CRM_Contact_BAO_Contact_Utils
::clearCurrentEmployer($relationship->contact_id_a
);
677 return $relationship;
681 * Delete the relationship.
688 public static function del($id) {
689 // delete from relationship table
690 CRM_Utils_Hook
::pre('delete', 'Relationship', $id, CRM_Core_DAO
::$_nullArray);
692 $relationship = self
::clearCurrentEmployer($id, CRM_Core_Action
::DELETE
);
693 if (CRM_Core_Permission
::access('CiviMember')) {
694 // create $params array which isrequired to delete memberships
695 // of the related contacts.
697 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b",
698 'contact_check' => array($relationship->contact_id_b
=> 1),
702 // calling relatedMemberships to delete the memberships of
704 self
::relatedMemberships($relationship->contact_id_a
,
707 CRM_Core_Action
::DELETE
,
712 $relationship->delete();
713 CRM_Core_Session
::setStatus(ts('Selected relationship has been deleted successfully.'), ts('Record Deleted'), 'success');
715 CRM_Utils_Hook
::post('delete', 'Relationship', $id, $relationship);
717 // delete the recently created Relationship
718 $relationshipRecent = array(
720 'type' => 'Relationship',
722 CRM_Utils_Recent
::del($relationshipRecent);
724 return $relationship;
728 * Disable/enable the relationship.
734 * @param array $params
736 * @param bool $active
738 public static function disableEnableRelationship($id, $action, $params = array(), $ids = array(), $active = FALSE) {
739 $relationship = self
::clearCurrentEmployer($id, $action);
742 // create $params array which is required to delete memberships
743 // of the related contacts.
744 if (empty($params)) {
746 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b",
747 'contact_check' => array($relationship->contact_id_b
=> 1),
750 $contact_id_a = empty($params['contact_id_a']) ?
$relationship->contact_id_a
: $params['contact_id_a'];
751 // calling relatedMemberships to delete/add the memberships of
753 if ($action & CRM_Core_Action
::DISABLE
) {
754 CRM_Contact_BAO_Relationship
::relatedMemberships($contact_id_a,
757 CRM_Core_Action
::DELETE
,
761 elseif ($action & CRM_Core_Action
::ENABLE
) {
762 $ids['contact'] = empty($ids['contact']) ?
$contact_id_a : $ids['contact'];
763 CRM_Contact_BAO_Relationship
::relatedMemberships($contact_id_a,
766 empty($params['id']) ? CRM_Core_Action
::ADD
: CRM_Core_Action
::UPDATE
,
774 * Delete the object records that are associated with this contact.
776 * @param int $contactId
777 * Id of the contact to delete.
779 public static function deleteContact($contactId) {
780 $relationship = new CRM_Contact_DAO_Relationship();
781 $relationship->contact_id_a
= $contactId;
782 $relationship->delete();
784 $relationship = new CRM_Contact_DAO_Relationship();
785 $relationship->contact_id_b
= $contactId;
786 $relationship->delete();
788 CRM_Contact_BAO_Household
::updatePrimaryContact(NULL, $contactId);
792 * Get the other contact in a relationship.
797 * $returns returns the contact ids in the realtionship
799 * @return \CRM_Contact_DAO_Relationship
801 public static function getRelationshipByID($id) {
802 $relationship = new CRM_Contact_DAO_Relationship();
804 $relationship->id
= $id;
805 $relationship->selectAdd();
806 $relationship->selectAdd('contact_id_a, contact_id_b');
807 $relationship->find(TRUE);
809 return $relationship;
813 * Check if the relationship type selected between two contacts is correct.
815 * @param int $contact_a
817 * @param int $contact_b
819 * @param int $relationshipTypeId
820 * Relationship type id.
823 * true if it is valid relationship else false
825 public static function checkRelationshipType($contact_a, $contact_b, $relationshipTypeId) {
826 $relationshipType = new CRM_Contact_DAO_RelationshipType();
827 $relationshipType->id
= $relationshipTypeId;
828 $relationshipType->selectAdd();
829 $relationshipType->selectAdd('contact_type_a, contact_type_b, contact_sub_type_a, contact_sub_type_b');
830 if ($relationshipType->find(TRUE)) {
831 $contact_type_a = CRM_Contact_BAO_Contact
::getContactType($contact_a);
832 $contact_type_b = CRM_Contact_BAO_Contact
::getContactType($contact_b);
834 $contact_sub_type_a = CRM_Contact_BAO_Contact
::getContactSubType($contact_a);
835 $contact_sub_type_b = CRM_Contact_BAO_Contact
::getContactSubType($contact_b);
837 if (((!$relationshipType->contact_type_a
) ||
($relationshipType->contact_type_a
== $contact_type_a)) &&
838 ((!$relationshipType->contact_type_b
) ||
($relationshipType->contact_type_b
== $contact_type_b)) &&
839 ((!$relationshipType->contact_sub_type_a
) ||
(in_array($relationshipType->contact_sub_type_a
,
842 ((!$relationshipType->contact_sub_type_b
) ||
(in_array($relationshipType->contact_sub_type_b
,
856 * This function does the validtion for valid relationship.
858 * @param array $params
859 * This array contains the values there are subitted by the form.
861 * The array that holds all the db ids.
862 * @param int $contactId
863 * This is contact id for adding relationship.
867 public static function checkValidRelationship($params, $ids, $contactId) {
869 // function to check if the relationship selected is correct
870 // i.e. employer relationship can exit between Individual and Organization (not between Individual and Individual)
871 if (!CRM_Contact_BAO_Relationship
::checkRelationshipType($params['contact_id_a'], $params['contact_id_b'],
872 $params['relationship_type_id'])) {
873 $errors = 'Please select valid relationship between these two contacts.';
879 * This function checks for duplicate relationship.
881 * @param array $params
882 * (reference ) an assoc array of name/value pairs.
884 * This the id of the contact whom we are adding relationship.
885 * @param int $contactId
886 * This is contact id for adding relationship.
887 * @param int $relationshipId
888 * This is relationship id for the contact.
891 * true if record exists else false
893 public static function checkDuplicateRelationship(&$params, $id, $contactId = 0, $relationshipId = 0) {
894 $relationshipTypeId = CRM_Utils_Array
::value('relationship_type_id', $params);
895 list($type) = explode('_', $relationshipTypeId);
899 FROM civicrm_relationship
900 WHERE relationship_type_id = " . CRM_Utils_Type
::escape($type, 'Integer');
903 * CRM-11792 - date fields from API are in ISO format, but this function
904 * supports date arrays BAO has increasingly standardised to ISO format
905 * so I believe this function should support ISO rather than make API
906 * format it - however, need to support array format for now to avoid breakage
907 * @ time of writing this function is called from Relationship::legacyCreateMultiple (twice)
908 * CRM_BAO_Contact_Utils::clearCurrentEmployer (seemingly without dates)
909 * CRM_Contact_Form_Task_AddToOrganization::postProcess &
910 * CRM_Contact_Form_Task_AddToHousehold::postProcess
911 * (I don't think the last 2 support dates but not sure
914 $dateFields = array('end_date', 'start_date');
915 foreach ($dateFields as $dateField) {
916 if (array_key_exists($dateField, $params)) {
917 if (empty($params[$dateField]) ||
$params[$dateField] == 'null') {
918 //this is most likely coming from an api call & probably loaded
919 // from the DB to deal with some of the
920 // other myriad of excessive checks still in place both in
921 // the api & the create functions
922 $queryString .= " AND $dateField IS NULL";
925 elseif (is_array($params[$dateField])) {
926 $queryString .= " AND $dateField = " .
927 CRM_Utils_Type
::escape(CRM_Utils_Date
::format($params[$dateField]), 'Date');
930 $queryString .= " AND $dateField = " .
931 CRM_Utils_Type
::escape($params[$dateField], 'Date');
937 " AND ( ( contact_id_a = " . CRM_Utils_Type
::escape($id, 'Integer') .
938 " AND contact_id_b = " . CRM_Utils_Type
::escape($contactId, 'Integer') .
939 " ) OR ( contact_id_a = " . CRM_Utils_Type
::escape($contactId, 'Integer') .
940 " AND contact_id_b = " . CRM_Utils_Type
::escape($id, 'Integer') . " ) ) ";
942 //if caseId is provided, include it duplicate checking.
943 if ($caseId = CRM_Utils_Array
::value('case_id', $params)) {
944 $queryString .= " AND case_id = " . CRM_Utils_Type
::escape($caseId, 'Integer');
947 if ($relationshipId) {
948 $queryString .= " AND id !=" . CRM_Utils_Type
::escape($relationshipId, 'Integer');
951 $relationship = new CRM_Contact_BAO_Relationship();
952 $relationship->query($queryString);
953 while ($relationship->fetch()) {
954 // Check whether the custom field values are identical.
955 $result = self
::checkDuplicateCustomFields($params, $relationship->id
);
957 $relationship->free();
961 $relationship->free();
966 * this function checks whether the values of the custom fields in $params are
967 * the same as the values of the custom fields of the relation with given
970 * @param array $params (reference) an assoc array of name/value pairs
971 * @param int $relationshipId ID of an existing duplicate relation
973 * @return boolean true if custom field values are identical
977 private static function checkDuplicateCustomFields(&$params, $relationshipId) {
978 // Get the custom values of the existing relationship.
979 $existingValues = CRM_Core_BAO_CustomValueTable
::getEntityValues($relationshipId, 'Relationship');
980 // Create a similar array for the new relationship.
981 $newValues = array();
982 if (array_key_exists('custom', $params)) {
983 // $params['custom'] seems to be an array. Each value is again an array.
984 // This array contains one value (key -1), and this value seems to be
985 // an array with the information about the custom value.
986 foreach ($params['custom'] as $value) {
987 foreach ($value as $customValue) {
988 $newValues[$customValue['custom_field_id']] = $customValue['value'];
993 // Calculate difference between arrays. If the only key-value pairs
994 // that are in one array but not in the other are empty, the
995 // custom fields are considered to be equal.
996 // See https://github.com/civicrm/civicrm-core/pull/6515#issuecomment-137985667
997 $diff1 = array_diff_assoc($existingValues, $newValues);
998 $diff2 = array_diff_assoc($newValues, $existingValues);
1000 return !array_filter($diff1) && !array_filter($diff2);
1004 * Update the is_active flag in the db.
1007 * Id of the database record.
1008 * @param bool $is_active
1009 * Value we want to set the is_active field.
1011 * @throws CiviCRM_API3_Exception
1013 * DAO object on success, null otherwise
1015 public static function setIsActive($id, $is_active) {
1016 // as both the create & add functions have a bunch of logic in them that
1017 // doesn't seem to cope with a normal update we will call the api which
1018 // has tested handling for this
1019 // however, a longer term solution would be to simplify the add, create & api functions
1020 // to be more standard. It is debatable @ that point whether it's better to call the BAO
1021 // direct as the api is more tested.
1022 $result = civicrm_api('relationship', 'create', array(
1024 'is_active' => $is_active,
1028 if (is_array($result) && !empty($result['is_error']) && $result['error_message'] != 'Duplicate Relationship') {
1029 throw new CiviCRM_API3_Exception($result['error_message'], CRM_Utils_Array
::value('error_code', $result, 'undefined'), $result);
1036 * Fetch a relationship object and store the values in the values array.
1038 * @param array $params
1039 * Input parameters to find object.
1040 * @param array $values
1041 * Output values of the object.
1044 * (reference) the values that could be potentially assigned to smarty
1046 public static function &getValues(&$params, &$values) {
1047 if (empty($params)) {
1052 // get the specific number of relationship or all relationships.
1053 if (!empty($params['numRelationship'])) {
1054 $v['data'] = &CRM_Contact_BAO_Relationship
::getRelationship($params['contact_id'], NULL, $params['numRelationship']);
1057 $v['data'] = CRM_Contact_BAO_Relationship
::getRelationship($params['contact_id']);
1060 // get the total count of relationships
1061 $v['totalCount'] = count($v['data']);
1063 $values['relationship']['data'] = &$v['data'];
1064 $values['relationship']['totalCount'] = &$v['totalCount'];
1070 * Helper function to form the sql for relationship retrieval.
1072 * @param int $contactId
1074 * @param int $status
1075 * (check const at top of file).
1076 * @param int $numRelationship
1077 * No of relationships to display (limit).
1079 * Get the no of relationships.
1080 * $param int $relationshipId relationship id
1081 * @param int $relationshipId
1082 * @param string $direction
1083 * The direction we are interested in a_b or b_a.
1084 * @param array $params
1085 * Array of extra values including relationship_type_id per api spec.
1088 * [select, from, where]
1090 public static function makeURLClause($contactId, $status, $numRelationship, $count, $relationshipId, $direction, $params = array()) {
1091 $select = $from = $where = '';
1095 if ($direction == 'a_b') {
1096 $select .= ' SELECT count(DISTINCT civicrm_relationship.id) as cnt1, 0 as cnt2 ';
1099 $select .= ' SELECT 0 as cnt1, count(DISTINCT civicrm_relationship.id) as cnt2 ';
1103 $select .= ' SELECT civicrm_relationship.id as civicrm_relationship_id,
1104 civicrm_contact.sort_name as sort_name,
1105 civicrm_contact.display_name as display_name,
1106 civicrm_contact.job_title as job_title,
1107 civicrm_contact.employer_id as employer_id,
1108 civicrm_contact.organization_name as organization_name,
1109 civicrm_address.street_address as street_address,
1110 civicrm_address.city as city,
1111 civicrm_address.postal_code as postal_code,
1112 civicrm_state_province.abbreviation as state,
1113 civicrm_country.name as country,
1114 civicrm_email.email as email,
1115 civicrm_contact.contact_type as contact_type,
1116 civicrm_phone.phone as phone,
1117 civicrm_contact.id as civicrm_contact_id,
1118 civicrm_relationship.contact_id_b as contact_id_b,
1119 civicrm_relationship.contact_id_a as contact_id_a,
1120 civicrm_relationship_type.id as civicrm_relationship_type_id,
1121 civicrm_relationship.start_date as start_date,
1122 civicrm_relationship.end_date as end_date,
1123 civicrm_relationship.description as description,
1124 civicrm_relationship.is_active as is_active,
1125 civicrm_relationship.is_permission_a_b as is_permission_a_b,
1126 civicrm_relationship.is_permission_b_a as is_permission_b_a,
1127 civicrm_relationship.case_id as case_id';
1129 if ($direction == 'a_b') {
1130 $select .= ', civicrm_relationship_type.label_a_b as label_a_b,
1131 civicrm_relationship_type.label_b_a as relation ';
1134 $select .= ', civicrm_relationship_type.label_a_b as label_a_b,
1135 civicrm_relationship_type.label_a_b as relation ';
1140 FROM civicrm_relationship
1141 INNER JOIN civicrm_relationship_type ON ( civicrm_relationship.relationship_type_id = civicrm_relationship_type.id )
1142 INNER JOIN civicrm_contact ";
1143 if ($direction == 'a_b') {
1144 $from .= 'ON ( civicrm_contact.id = civicrm_relationship.contact_id_a ) ';
1147 $from .= 'ON ( civicrm_contact.id = civicrm_relationship.contact_id_b ) ';
1150 LEFT JOIN civicrm_address ON (civicrm_address.contact_id = civicrm_contact.id AND civicrm_address.is_primary = 1)
1151 LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = civicrm_contact.id AND civicrm_phone.is_primary = 1)
1152 LEFT JOIN civicrm_email ON (civicrm_email.contact_id = civicrm_contact.id AND civicrm_email.is_primary = 1)
1153 LEFT JOIN civicrm_state_province ON (civicrm_address.state_province_id = civicrm_state_province.id)
1154 LEFT JOIN civicrm_country ON (civicrm_address.country_id = civicrm_country.id)
1156 $where = 'WHERE ( 1 )';
1158 if ($direction == 'a_b') {
1159 $where .= ' AND civicrm_relationship.contact_id_b = ' . CRM_Utils_Type
::escape($contactId, 'Positive');
1162 $where .= ' AND civicrm_relationship.contact_id_a = ' . CRM_Utils_Type
::escape($contactId, 'Positive') . '
1163 AND civicrm_relationship.contact_id_a != civicrm_relationship.contact_id_b ';
1166 if ($relationshipId) {
1167 $where .= ' AND civicrm_relationship.id = ' . CRM_Utils_Type
::escape($relationshipId, 'Positive');
1170 $date = date('Y-m-d');
1171 if ($status == self
::PAST
) {
1172 //this case for showing past relationship
1173 $where .= ' AND civicrm_relationship.is_active = 1 ';
1174 $where .= " AND civicrm_relationship.end_date < '" . $date . "'";
1176 elseif ($status == self
::DISABLED
) {
1177 // this case for showing disabled relationship
1178 $where .= ' AND civicrm_relationship.is_active = 0 ';
1180 elseif ($status == self
::CURRENT
) {
1181 //this case for showing current relationship
1182 $where .= ' AND civicrm_relationship.is_active = 1 ';
1183 $where .= " AND (civicrm_relationship.end_date >= '" . $date . "' OR civicrm_relationship.end_date IS NULL) ";
1185 elseif ($status == self
::INACTIVE
) {
1186 //this case for showing inactive relationships
1187 $where .= " AND (civicrm_relationship.end_date < '" . $date . "'";
1188 $where .= ' OR civicrm_relationship.is_active = 0 )';
1192 $where .= ' AND civicrm_contact.is_deleted = 0';
1193 if (!empty($params['membership_type_id']) && empty($params['relationship_type_id'])) {
1194 $where .= self
::membershipTypeToRelationshipTypes($params, $direction);
1196 if (!empty($params['relationship_type_id'])) {
1197 if (is_array($params['relationship_type_id'])) {
1198 $where .= " AND " . CRM_Core_DAO
::createSQLFilter('relationship_type_id', $params['relationship_type_id'], 'Integer');
1201 $where .= ' AND relationship_type_id = ' . CRM_Utils_Type
::escape($params['relationship_type_id'], 'Positive');
1204 if ($direction == 'a_b') {
1205 $where .= ' ) UNION ';
1211 return array($select, $from, $where);
1215 * Get a list of relationships.
1217 * @param int $contactId
1219 * @param int $status
1220 * 1: Past 2: Disabled 3: Current.
1221 * @param int $numRelationship
1222 * No of relationships to display (limit).
1224 * Get the no of relationships.
1225 * @param int $relationshipId
1226 * @param array $links
1227 * the list of links to display
1228 * @param int $permissionMask
1229 * the permission mask to be applied for the actions
1230 * @param bool $permissionedContact
1231 * to return only permissioned Contact
1232 * @param array $params
1235 * relationship records
1237 public static function getRelationship(
1239 $status = 0, $numRelationship = 0,
1240 $count = 0, $relationshipId = 0,
1241 $links = NULL, $permissionMask = NULL,
1242 $permissionedContact = FALSE,
1246 if (!$contactId && !$relationshipId) {
1250 list($select1, $from1, $where1) = self
::makeURLClause($contactId, $status, $numRelationship,
1251 $count, $relationshipId, 'a_b', $params
1253 list($select2, $from2, $where2) = self
::makeURLClause($contactId, $status, $numRelationship,
1254 $count, $relationshipId, 'b_a', $params
1257 $order = $limit = '';
1259 if (empty($params['sort'])) {
1260 $order = ' ORDER BY civicrm_relationship_type_id, sort_name ';
1263 $order = " ORDER BY {$params['sort']} ";
1267 if (!empty($params['offset']) && $params['offset'] > 0) {
1268 $offset = $params['offset'];
1271 if ($numRelationship) {
1272 $limit = " LIMIT {$offset}, $numRelationship";
1276 // building the query string
1277 $queryString = $select1 . $from1 . $where1 . $select2 . $from2 . $where2 . $order . $limit;
1279 $relationship = new CRM_Contact_DAO_Relationship();
1281 $relationship->query($queryString);
1284 $relationshipCount = 0;
1285 while ($relationship->fetch()) {
1286 $relationshipCount +
= $relationship->cnt1 +
$relationship->cnt2
;
1288 return $relationshipCount;
1293 if ($status != self
::INACTIVE
) {
1295 $mask = array_sum(array_keys($links));
1296 if ($mask & CRM_Core_Action
::DISABLE
) {
1297 $mask -= CRM_Core_Action
::DISABLE
;
1299 if ($mask & CRM_Core_Action
::ENABLE
) {
1300 $mask -= CRM_Core_Action
::ENABLE
;
1303 if ($status == self
::CURRENT
) {
1304 $mask |
= CRM_Core_Action
::DISABLE
;
1306 elseif ($status == self
::DISABLED
) {
1307 $mask |
= CRM_Core_Action
::ENABLE
;
1310 // temporary hold the value of $mask.
1314 while ($relationship->fetch()) {
1315 $rid = $relationship->civicrm_relationship_id
;
1316 $cid = $relationship->civicrm_contact_id
;
1318 if ($permissionedContact &&
1319 (!CRM_Contact_BAO_Contact_Permission
::allow($cid))
1323 if ($status != self
::INACTIVE
&& $links) {
1324 // assign the original value to $mask
1326 // display action links if $cid has edit permission for the relationship.
1327 if (!($permissionMask & CRM_Core_Permission
::EDIT
) && CRM_Contact_BAO_Contact_Permission
::allow($cid, CRM_Core_Permission
::EDIT
)) {
1328 $permissions[] = CRM_Core_Permission
::EDIT
;
1329 $permissions[] = CRM_Core_Permission
::DELETE
;
1330 $permissionMask = CRM_Core_Action
::mask($permissions);
1332 $mask = $mask & $permissionMask;
1334 $values[$rid]['id'] = $rid;
1335 $values[$rid]['cid'] = $cid;
1336 $values[$rid]['contact_id_a'] = $relationship->contact_id_a
;
1337 $values[$rid]['contact_id_b'] = $relationship->contact_id_b
;
1338 $values[$rid]['contact_type'] = $relationship->contact_type
;
1339 $values[$rid]['relationship_type_id'] = $relationship->civicrm_relationship_type_id
;
1340 $values[$rid]['relation'] = $relationship->relation
;
1341 $values[$rid]['name'] = $relationship->sort_name
;
1342 $values[$rid]['display_name'] = $relationship->display_name
;
1343 $values[$rid]['job_title'] = $relationship->job_title
;
1344 $values[$rid]['email'] = $relationship->email
;
1345 $values[$rid]['phone'] = $relationship->phone
;
1346 $values[$rid]['employer_id'] = $relationship->employer_id
;
1347 $values[$rid]['organization_name'] = $relationship->organization_name
;
1348 $values[$rid]['country'] = $relationship->country
;
1349 $values[$rid]['city'] = $relationship->city
;
1350 $values[$rid]['state'] = $relationship->state
;
1351 $values[$rid]['start_date'] = $relationship->start_date
;
1352 $values[$rid]['end_date'] = $relationship->end_date
;
1353 $values[$rid]['description'] = $relationship->description
;
1354 $values[$rid]['is_active'] = $relationship->is_active
;
1355 $values[$rid]['is_permission_a_b'] = $relationship->is_permission_a_b
;
1356 $values[$rid]['is_permission_b_a'] = $relationship->is_permission_b_a
;
1357 $values[$rid]['case_id'] = $relationship->case_id
;
1360 $values[$rid]['status'] = $status;
1363 $values[$rid]['civicrm_relationship_type_id'] = $relationship->civicrm_relationship_type_id
;
1365 if ($relationship->contact_id_a
== $contactId) {
1366 $values[$rid]['rtype'] = 'a_b';
1369 $values[$rid]['rtype'] = 'b_a';
1375 'rtype' => $values[$rid]['rtype'],
1376 'cid' => $contactId,
1377 'cbid' => $values[$rid]['cid'],
1378 'caseid' => $values[$rid]['case_id'],
1379 'clientid' => $contactId,
1382 if ($status == self
::INACTIVE
) {
1383 // setting links for inactive relationships
1384 $mask = array_sum(array_keys($links));
1385 if (!$values[$rid]['is_active']) {
1386 $mask -= CRM_Core_Action
::DISABLE
;
1389 $mask -= CRM_Core_Action
::ENABLE
;
1390 $mask -= CRM_Core_Action
::DISABLE
;
1392 $mask = $mask & $permissionMask;
1395 // Give access to manage case link by copying to MAX_ACTION index temporarily, depending on case permission of user.
1396 if ($values[$rid]['case_id']) {
1397 // Borrowed logic from CRM_Case_Page_Tab
1398 $hasCaseAccess = FALSE;
1399 if (CRM_Core_Permission
::check('access all cases and activities')) {
1400 $hasCaseAccess = TRUE;
1403 $userCases = CRM_Case_BAO_Case
::getCases(FALSE);
1404 if (array_key_exists($values[$rid]['case_id'], $userCases)) {
1405 $hasCaseAccess = TRUE;
1409 if ($hasCaseAccess) {
1410 // give access by copying to MAX_ACTION temporarily, otherwise leave at NONE which won't display
1411 $links[CRM_Core_Action
::MAX_ACTION
] = $links[CRM_Core_Action
::NONE
];
1412 $links[CRM_Core_Action
::MAX_ACTION
]['name'] = ts('Manage Case #%1', array(1 => $values[$rid]['case_id']));
1413 $links[CRM_Core_Action
::MAX_ACTION
]['class'] = 'no-popup';
1415 // Also make sure we have the right client cid since can get here from multiple relationship tabs.
1416 if ($values[$rid]['rtype'] == 'b_a') {
1417 $replace['clientid'] = $values[$rid]['cid'];
1422 $values[$rid]['action'] = CRM_Core_Action
::formLink(
1428 'relationship.selector.row',
1431 unset($links[CRM_Core_Action
::MAX_ACTION
]);
1435 $relationship->free();
1441 * Get get list of relationship type based on the target contact type.
1443 * @param string $targetContactType
1444 * It's valid contact tpye(may be Individual , Organization , Household).
1447 * array reference of all relationship types with context to current contact type .
1449 static public function getRelationType($targetContactType) {
1450 $relationshipType = array();
1451 $allRelationshipType = CRM_Core_PseudoConstant
::relationshipType();
1453 foreach ($allRelationshipType as $key => $type) {
1454 if ($type['contact_type_b'] == $targetContactType) {
1455 $relationshipType[$key . '_a_b'] = $type['label_a_b'];
1459 return $relationshipType;
1463 * Create / update / delete membership for related contacts.
1465 * This function will create/update/delete membership for related
1466 * contact based on 1) contact have active membership 2) that
1467 * membership is is extedned by the same relationship type to that
1468 * of the existing relationship.
1470 * @param int $contactId
1472 * @param array $params
1473 * array of values submitted by POST.
1476 * @param \const|int $action which action called this function
1478 * @param bool $active
1480 * @throws \CRM_Core_Exception
1482 public static function relatedMemberships($contactId, &$params, $ids, $action = CRM_Core_Action
::ADD
, $active = TRUE) {
1483 // Check the end date and set the status of the relationship
1485 $status = self
::CURRENT
;
1486 $targetContact = $targetContact = CRM_Utils_Array
::value('contact_check', $params, array());
1487 $today = date('Ymd');
1489 // If a relationship hasn't yet started, just return for now
1490 // TODO: handle edge-case of updating start_date of an existing relationship
1491 if (!empty($params['start_date'])) {
1492 $startDate = substr(CRM_Utils_Date
::format($params['start_date']), 0, 8);
1493 if ($today < $startDate) {
1498 if (!empty($params['end_date'])) {
1499 $endDate = substr(CRM_Utils_Date
::format($params['end_date']), 0, 8);
1500 if ($today > $endDate) {
1501 $status = self
::PAST
;
1505 if (($action & CRM_Core_Action
::ADD
) && ($status & self
::PAST
)) {
1506 // If relationship is PAST and action is ADD, do nothing.
1510 $rel = explode('_', $params['relationship_type_id']);
1512 $relTypeId = $rel[0];
1513 if (!empty($rel[1])) {
1514 $relDirection = "_{$rel[1]}_{$rel[2]}";
1517 // this call is coming from somewhere where the direction was resolved early on (e.g an api call)
1518 // so we can assume _a_b
1519 $relDirection = "_a_b";
1520 $targetContact = array($params['contact_id_b'] => 1);
1523 if (($action & CRM_Core_Action
::ADD
) ||
1524 ($action & CRM_Core_Action
::DELETE
)
1526 $contact = $contactId;
1528 elseif ($action & CRM_Core_Action
::UPDATE
) {
1529 $contact = $ids['contact'];
1530 $targetContact = array($ids['contactTarget'] => 1);
1533 // Build the 'values' array for
1536 // This will allow us to check if either of the contacts in
1537 // relationship have active memberships.
1542 $values[$contact] = array(
1543 'relatedContacts' => $targetContact,
1544 'relationshipTypeId' => $relTypeId,
1545 'relationshipTypeDirection' => $relDirection,
1548 if (!empty($targetContact)) {
1549 foreach ($targetContact as $cid => $donCare) {
1550 $values[$cid] = array(
1551 'relatedContacts' => array($contact => 1),
1552 'relationshipTypeId' => $relTypeId,
1555 $relTypeParams = array('id' => $relTypeId);
1556 $relTypeValues = array();
1557 CRM_Contact_BAO_RelationshipType
::retrieve($relTypeParams, $relTypeValues);
1559 if (CRM_Utils_Array
::value('name_a_b', $relTypeValues) == CRM_Utils_Array
::value('name_b_a', $relTypeValues)) {
1560 $values[$cid]['relationshipTypeDirection'] = '_a_b';
1563 $values[$cid]['relationshipTypeDirection'] = ($relDirection == '_a_b') ?
'_b_a' : '_a_b';
1568 // CRM-15829 UPDATES
1569 // If we're looking for active memberships we must consider pending (id: 5) ones too.
1570 // Hence we can't just call CRM_Member_BAO_Membership::getValues below with the active flag, is it would completely miss pending relatioships.
1571 // 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.
1572 $pendingStatusId = array_search('Pending', CRM_Member_PseudoConstant
::membershipStatus());
1574 $query = 'SELECT * FROM `civicrm_membership_status`';
1576 $query .= ' WHERE `is_current_member` = 1 OR `id` = %1 ';
1579 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($pendingStatusId, 'Integer')));
1581 while ($dao->fetch()) {
1582 $membershipStatusRecordIds[$dao->id
] = $dao->id
;
1585 // Now get the active memberships for all the contacts.
1586 // If contact have any valid membership(s), then add it to
1588 foreach ($values as $cid => $subValues) {
1589 $memParams = array('contact_id' => $cid);
1590 $memberships = array();
1592 // CRM-15829 UPDATES
1593 // 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.
1594 CRM_Member_BAO_Membership
::getValues($memParams, $memberships, FALSE, TRUE);
1596 // CRM-15829 UPDATES
1597 // filter out the memberships returned by CRM_Member_BAO_Membership::getValues based on the status IDs fetched on line ~1462
1598 foreach ($memberships as $key => $membership) {
1600 if (!isset($memberships[$key]['status_id'])) {
1604 $membershipStatusId = $memberships[$key]['status_id'];
1605 if (!isset($membershipStatusRecordIds[$membershipStatusId])) {
1606 unset($memberships[$key]);
1610 if (empty($memberships)) {
1614 //get ownerMembershipIds for related Membership
1615 //this is to handle memberships being deleted and recreated
1616 if (!empty($memberships['owner_membership_ids'])) {
1617 $ownerMemIds[$cid] = $memberships['owner_membership_ids'];
1618 unset($memberships['owner_membership_ids']);
1621 $values[$cid]['memberships'] = $memberships;
1623 $deceasedStatusId = array_search('Deceased', CRM_Member_PseudoConstant
::membershipStatus());
1625 // done with 'values' array.
1626 // Finally add / edit / delete memberships for the related contacts
1628 foreach ($values as $cid => $details) {
1629 if (!array_key_exists('memberships', $details)) {
1633 $relatedContacts = array_keys(CRM_Utils_Array
::value('relatedContacts', $details, array()));
1634 $mainRelatedContactId = reset($relatedContacts);
1636 foreach ($details['memberships'] as $membershipId => $membershipValues) {
1637 $relTypeIds = array();
1638 if ($action & CRM_Core_Action
::DELETE
) {
1639 // Delete memberships of the related contacts only if relationship type exists for membership type
1641 SELECT relationship_type_id, relationship_direction
1642 FROM civicrm_membership_type
1643 WHERE id = {$membershipValues['membership_type_id']}";
1644 $dao = CRM_Core_DAO
::executeQuery($query);
1645 $relTypeDirs = array();
1646 while ($dao->fetch()) {
1647 $relTypeId = $dao->relationship_type_id
;
1648 $relDirection = $dao->relationship_direction
;
1650 $relTypeIds = explode(CRM_Core_DAO
::VALUE_SEPARATOR
, $relTypeId);
1651 if (in_array($values[$cid]['relationshipTypeId'], $relTypeIds
1652 //CRM-16300 check if owner membership exist for related membership
1653 ) && !empty($membershipValues['owner_membership_id']) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) {
1654 CRM_Member_BAO_Membership
::deleteRelatedMemberships($membershipValues['owner_membership_id'], $membershipValues['membership_contact_id']);
1658 if (($action & CRM_Core_Action
::UPDATE
) &&
1659 ($status & self
::PAST
) &&
1660 ($membershipValues['owner_membership_id'])
1662 // If relationship is PAST and action is UPDATE
1663 // then delete the RELATED membership
1664 CRM_Member_BAO_Membership
::deleteRelatedMemberships($membershipValues['owner_membership_id'],
1665 $membershipValues['membership_contact_id']
1670 // add / edit the memberships for related
1673 // Get the Membership Type Details.
1674 $membershipType = CRM_Member_BAO_MembershipType
::getMembershipTypeDetails($membershipValues['membership_type_id']);
1675 // Check if contact's relationship type exists in membership type
1676 $relTypeDirs = array();
1677 if (!empty($membershipType['relationship_type_id'])) {
1678 $relTypeIds = explode(CRM_Core_DAO
::VALUE_SEPARATOR
, $membershipType['relationship_type_id']);
1680 if (!empty($membershipType['relationship_direction'])) {
1681 $relDirections = explode(CRM_Core_DAO
::VALUE_SEPARATOR
, $membershipType['relationship_direction']);
1683 foreach ($relTypeIds as $key => $value) {
1684 $relTypeDirs[] = $value . '_' . $relDirections[$key];
1686 $relTypeDir = $details['relationshipTypeId'] . $details['relationshipTypeDirection'];
1687 if (in_array($relTypeDir, $relTypeDirs)) {
1688 // Check if relationship being created/updated is
1689 // similar to that of membership type's
1692 $membershipValues['owner_membership_id'] = $membershipId;
1693 unset($membershipValues['id']);
1694 unset($membershipValues['membership_contact_id']);
1695 unset($membershipValues['contact_id']);
1696 unset($membershipValues['membership_id']);
1697 foreach ($details['relatedContacts'] as $relatedContactId => $donCare) {
1698 $membershipValues['contact_id'] = $relatedContactId;
1699 if ($deceasedStatusId &&
1700 CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $relatedContactId, 'is_deceased')
1702 $membershipValues['status_id'] = $deceasedStatusId;
1703 $membershipValues['skipStatusCal'] = TRUE;
1710 if (!empty($membershipValues[$dateField])) {
1711 $membershipValues[$dateField] = CRM_Utils_Date
::processDate($membershipValues[$dateField]);
1715 if ($action & CRM_Core_Action
::UPDATE
) {
1716 //if updated relationship is already related to contact don't delete existing inherited membership
1717 if (in_array($relTypeId, $relTypeIds
1718 ) && !empty($values[$relatedContactId]['memberships']) && !empty($ownerMemIds
1719 ) && in_array($membershipValues['owner_membership_id'], $ownerMemIds[$relatedContactId])) {
1723 //delete the membership record for related
1724 //contact before creating new membership record.
1725 CRM_Member_BAO_Membership
::deleteRelatedMemberships($membershipId, $relatedContactId);
1728 // check whether we have some related memberships still available
1731 FROM civicrm_membership
1732 LEFT JOIN civicrm_membership_status ON (civicrm_membership_status.id = civicrm_membership.status_id)
1733 WHERE membership_type_id = {$membershipValues['membership_type_id']} AND owner_membership_id = {$membershipValues['owner_membership_id']}
1734 AND is_current_member = 1";
1735 $result = CRM_Core_DAO
::singleValueQuery($query);
1736 if ($result < CRM_Utils_Array
::value('max_related', $membershipValues, PHP_INT_MAX
)) {
1737 CRM_Member_BAO_Membership
::create($membershipValues, CRM_Core_DAO
::$_nullArray);
1741 elseif ($action & CRM_Core_Action
::UPDATE
) {
1742 // if action is update and updated relationship do
1743 // not match with the existing
1744 // membership=>relationship then we need to
1745 // change the status of the membership record to expired for
1746 // previous relationship -- CRM-12078.
1747 // CRM-16087 we need to pass ownerMembershipId to isRelatedMembershipExpired function
1748 if (empty($params['relationship_ids']) && !empty($params['id'])) {
1749 $relIds = array($params['id']);
1752 $relIds = CRM_Utils_Array
::value('relationship_ids', $params);
1754 if (self
::isRelatedMembershipExpired($relTypeIds, $contactId, $mainRelatedContactId, $relTypeId,
1755 $relIds) && !empty($membershipValues['owner_membership_id']
1756 ) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) {
1757 $membershipValues['status_id'] = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_MembershipStatus', 'Expired', 'id', 'label');
1758 $type = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_MembershipType', $membershipValues['membership_type_id'], 'name', 'id');
1759 CRM_Member_BAO_Membership
::add($membershipValues);
1760 CRM_Core_Session
::setStatus(ts("Inherited membership {$type} status was changed to Expired due to the change in relationship type."), ts('Record Updated'), 'alert');
1768 * Helper function to check whether the membership is expired or not.
1770 * Function takes a list of related membership types and if it is not also passed a
1771 * relationship ID of that types evaluates whether the membership status should be changed to expired.
1773 * @param array $membershipTypeRelationshipTypeIDs
1774 * Relation type IDs related to the given membership type.
1775 * @param int $contactId
1776 * @param int $mainRelatedContactId
1777 * @param int $relTypeId
1778 * @param array $relIds
1782 public static function isRelatedMembershipExpired($membershipTypeRelationshipTypeIDs, $contactId, $mainRelatedContactId, $relTypeId, $relIds) {
1783 if (empty($membershipTypeRelationshipTypeIDs) ||
in_array($relTypeId, $membershipTypeRelationshipTypeIDs)) {
1787 if (empty($relIds)) {
1791 $relParamas = array(
1792 1 => array($contactId, 'Integer'),
1793 2 => array($mainRelatedContactId, 'Integer'),
1796 if ($contactId == $mainRelatedContactId) {
1797 $recordsFound = (int) CRM_Core_DAO
::singleValueQuery("SELECT COUNT(*) FROM civicrm_relationship WHERE relationship_type_id IN ( " . implode(',', $membershipTypeRelationshipTypeIDs) . " ) AND
1798 contact_id_a IN ( %1 ) OR contact_id_b IN ( %1 ) AND id IN (" . implode(',', $relIds) . ")", $relParamas);
1799 if ($recordsFound) {
1805 $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);
1807 if ($recordsFound) {
1815 * Get Current Employer for Contact.
1817 * @param $contactIds
1821 * array of the current employer
1823 public static function getCurrentEmployer($contactIds) {
1824 $contacts = implode(',', $contactIds);
1827 SELECT organization_name, id, employer_id
1828 FROM civicrm_contact
1829 WHERE id IN ( {$contacts} )
1832 $dao = CRM_Core_DAO
::executeQuery($query);
1833 $currentEmployer = array();
1834 while ($dao->fetch()) {
1835 $currentEmployer[$dao->id
]['org_id'] = $dao->employer_id
;
1836 $currentEmployer[$dao->id
]['org_name'] = $dao->organization_name
;
1839 return $currentEmployer;
1843 * Function to return list of permissioned contacts for a given contact and relationship type.
1845 * @param int $contactID
1846 * contact id whose permissioned contacts are to be found.
1847 * @param int $relTypeId
1848 * one or more relationship type id's.
1849 * @param string $name
1850 * @param string $contactType
1855 public static function getPermissionedContacts($contactID, $relTypeId = NULL, $name = NULL, $contactType = NULL) {
1856 $contacts = array();
1857 $args = array(1 => array($contactID, 'Integer'));
1858 $relationshipTypeClause = $contactTypeClause = '';
1861 // @todo relTypeId is only ever passed in as an int. Change this to reflect that -
1862 // probably being overly conservative by not doing so but working on stable release.
1863 $relationshipTypeClause = 'AND cr.relationship_type_id IN (%2) ';
1864 $args[2] = array($relTypeId, 'String');
1868 $contactTypeClause = ' AND cr.relationship_type_id = crt.id AND crt.contact_type_b = %3 ';
1869 $args[3] = array($contactType, 'String');
1873 SELECT cc.id as id, cc.sort_name as name
1874 FROM civicrm_relationship cr, civicrm_contact cc, civicrm_relationship_type crt
1876 cr.contact_id_a = %1 AND
1877 cr.is_permission_a_b = 1 AND
1878 IF(cr.end_date IS NULL, 1, (DATEDIFF( CURDATE( ), cr.end_date ) <= 0)) AND
1879 cr.is_active = 1 AND
1880 cc.id = cr.contact_id_b AND
1882 $relationshipTypeClause
1886 if (!empty($name)) {
1887 $name = CRM_Utils_Type
::escape($name, 'String');
1889 AND cc.sort_name LIKE '%$name%'";
1892 $dao = CRM_Core_DAO
::executeQuery($query, $args);
1893 while ($dao->fetch()) {
1894 $contacts[$dao->id
] = array(
1895 'name' => $dao->name
,
1896 'value' => $dao->id
,
1904 * Merge relationships from otherContact to mainContact.
1906 * Called during contact merge operation
1908 * @param int $mainId
1909 * Contact id of main contact record.
1910 * @param int $otherId
1911 * Contact id of record which is going to merge.
1912 * @param array $sqls
1913 * (reference) array of sql statements to append to.
1915 * @see CRM_Dedupe_Merger::cpTables()
1917 public static function mergeRelationships($mainId, $otherId, &$sqls) {
1918 // Delete circular relationships
1919 $sqls[] = "DELETE FROM civicrm_relationship
1920 WHERE (contact_id_a = $mainId AND contact_id_b = $otherId)
1921 OR (contact_id_b = $mainId AND contact_id_a = $otherId)";
1923 // Delete relationship from other contact if main contact already has that relationship
1924 $sqls[] = "DELETE r2
1925 FROM civicrm_relationship r1, civicrm_relationship r2
1926 WHERE r1.relationship_type_id = r2.relationship_type_id
1929 r1.contact_id_a = $mainId AND r2.contact_id_a = $otherId AND r1.contact_id_b = r2.contact_id_b
1930 OR r1.contact_id_b = $mainId AND r2.contact_id_b = $otherId AND r1.contact_id_a = r2.contact_id_a
1932 (r1.contact_id_a = $mainId AND r2.contact_id_b = $otherId AND r1.contact_id_b = r2.contact_id_a
1933 OR r1.contact_id_b = $mainId AND r2.contact_id_a = $otherId AND r1.contact_id_a = r2.contact_id_b)
1934 AND r1.relationship_type_id IN (SELECT id FROM civicrm_relationship_type WHERE name_b_a = name_a_b)
1938 // Move relationships
1939 $sqls[] = "UPDATE IGNORE civicrm_relationship SET contact_id_a = $mainId WHERE contact_id_a = $otherId";
1940 $sqls[] = "UPDATE IGNORE civicrm_relationship SET contact_id_b = $mainId WHERE contact_id_b = $otherId";
1942 // Move current employer id (name will get updated later)
1943 $sqls[] = "UPDATE civicrm_contact SET employer_id = $mainId WHERE employer_id = $otherId";
1947 * Set 'is_valid' field to false for all relationships whose end date is in the past, ie. are expired.
1950 * True on success, false if error is encountered.
1952 public static function disableExpiredRelationships() {
1953 $query = "SELECT id FROM civicrm_relationship WHERE is_active = 1 AND end_date < CURDATE()";
1955 $dao = CRM_Core_DAO
::executeQuery($query);
1956 while ($dao->fetch()) {
1957 $result = CRM_Contact_BAO_Relationship
::setIsActive($dao->id
, FALSE);
1958 // Result will be NULL if error occurred. We abort early if error detected.
1959 if ($result == NULL) {
1967 * Function filters the query by possible relationships for the membership type.
1969 * It is intended to be called when constructing queries for the api (reciprocal & non-reciprocal)
1970 * and to add clauses to limit the return to those relationships which COULD inherit a membership type
1971 * (as opposed to those who inherit a particular membership
1973 * @param array $params
1975 * @param null $direction
1977 * @return array|void
1979 public static function membershipTypeToRelationshipTypes(&$params, $direction = NULL) {
1980 $membershipType = civicrm_api3('membership_type', 'getsingle', array(
1981 'id' => $params['membership_type_id'],
1982 'return' => 'relationship_type_id, relationship_direction',
1984 $relationshipTypes = $membershipType['relationship_type_id'];
1985 if (empty($relationshipTypes)) {
1988 // if we don't have any contact data we can only filter on type
1989 if (empty($params['contact_id']) && empty($params['contact_id_a']) && empty($params['contact_id_a'])) {
1990 $params['relationship_type_id'] = array('IN' => $relationshipTypes);
1994 $relationshipDirections = (array) $membershipType['relationship_direction'];
1995 // if we have contact_id_a OR contact_id_b we can make a call here
1996 // if we have contact??
1997 foreach ($relationshipDirections as $index => $mtdirection) {
1998 if (isset($params['contact_id_a']) && $mtdirection == 'a_b' ||
$direction == 'a_b') {
1999 $types[] = $relationshipTypes[$index];
2001 if (isset($params['contact_id_b']) && $mtdirection == 'b_a' ||
$direction == 'b_a') {
2002 $types[] = $relationshipTypes[$index];
2005 if (!empty($types)) {
2006 $params['relationship_type_id'] = array('IN' => $types);
2008 elseif (!empty($clauses)) {
2009 return explode(' OR ', $clauses);
2012 // effectively setting it to return no results
2013 $params['relationship_type_id'] = 0;
2020 * Wrapper for contact relationship selector.
2022 * @param array $params
2023 * Associated array for params record id.
2026 * associated array of contact relationships
2028 public static function getContactRelationshipSelector(&$params) {
2029 // format the params
2030 $params['offset'] = ($params['page'] - 1) * $params['rp'];
2031 $params['sort'] = CRM_Utils_Array
::value('sortBy', $params);
2033 if ($params['context'] == 'past') {
2034 $relationshipStatus = CRM_Contact_BAO_Relationship
::INACTIVE
;
2036 elseif ($params['context'] == 'all') {
2037 $relationshipStatus = CRM_Contact_BAO_Relationship
::ALL
;
2040 $relationshipStatus = CRM_Contact_BAO_Relationship
::CURRENT
;
2043 // check logged in user for permission
2044 $page = new CRM_Core_Page();
2045 CRM_Contact_Page_View
::checkUserPermission($page, $params['contact_id']);
2046 $permissions = array($page->_permission
);
2047 if ($page->_permission
== CRM_Core_Permission
::EDIT
) {
2048 $permissions[] = CRM_Core_Permission
::DELETE
;
2050 $mask = CRM_Core_Action
::mask($permissions);
2052 $permissionedContacts = TRUE;
2053 if ($params['context'] != 'user') {
2054 $links = CRM_Contact_Page_View_Relationship
::links();
2057 $links = CRM_Contact_Page_View_UserDashBoard
::links();
2060 // get contact relationships
2061 $relationships = CRM_Contact_BAO_Relationship
::getRelationship($params['contact_id'],
2062 $relationshipStatus,
2063 $params['rp'], 0, 0,
2065 $permissionedContacts,
2069 $contactRelationships = array();
2070 $params['total'] = 0;
2071 if (!empty($relationships)) {
2072 // FIXME: we cannot directly determine total permissioned relationship, hence re-fire query
2073 $permissionedRelationships = CRM_Contact_BAO_Relationship
::getRelationship($params['contact_id'],
2074 $relationshipStatus,
2077 $permissionedContacts
2079 $params['total'] = count($permissionedRelationships);
2082 foreach ($relationships as $relationshipId => $values) {
2083 $relationship = array();
2085 $relationship['DT_RowId'] = $values['id'];
2086 $relationship['DT_RowClass'] = 'crm-entity';
2087 if ($values['is_active'] == 0) {
2088 $relationship['DT_RowClass'] .= ' disabled';
2091 $relationship['DT_RowAttr'] = array();
2092 $relationship['DT_RowAttr']['data-entity'] = 'relationship';
2093 $relationship['DT_RowAttr']['data-id'] = $values['id'];
2095 //Add image icon for related contacts: CRM-14919
2096 $icon = CRM_Contact_BAO_Contact_Utils
::getImage($values['contact_type'],
2100 $relationship['sort_name'] = $icon . ' ' . CRM_Utils_System
::href(
2102 'civicrm/contact/view',
2103 "reset=1&cid={$values['cid']}");
2105 $relationship['relation'] = CRM_Utils_System
::href(
2106 $values['relation'],
2107 'civicrm/contact/view/rel',
2108 "action=view&reset=1&cid={$values['cid']}&id={$values['id']}&rtype={$values['rtype']}");
2110 if ($params['context'] == 'current') {
2111 if (($params['contact_id'] == $values['contact_id_a'] AND $values['is_permission_a_b'] == 1) OR
2112 ($params['contact_id'] == $values['contact_id_b'] AND $values['is_permission_b_a'] == 1)
2114 $relationship['sort_name'] .= '<span id="permission-a-b" class="crm-marker permission-relationship"> *</span>';
2117 if (($values['cid'] == $values['contact_id_a'] AND $values['is_permission_a_b'] == 1) OR
2118 ($values['cid'] == $values['contact_id_b'] AND $values['is_permission_b_a'] == 1)
2120 $relationship['relation'] .= '<span id="permission-b-a" class="crm-marker permission-relationship"> *</span>';
2124 if (!empty($values['description'])) {
2125 $relationship['relation'] .= "<p class='description'>{$values['description']}</p>";
2128 $relationship['start_date'] = CRM_Utils_Date
::customFormat($values['start_date']);
2129 $relationship['end_date'] = CRM_Utils_Date
::customFormat($values['end_date']);
2130 $relationship['city'] = $values['city'];
2131 $relationship['state'] = $values['state'];
2132 $relationship['email'] = $values['email'];
2133 $relationship['phone'] = $values['phone'];
2134 $relationship['links'] = $values['action'];
2136 array_push($contactRelationships, $relationship);
2140 $relationshipsDT = array();
2141 $relationshipsDT['data'] = $contactRelationships;
2142 $relationshipsDT['recordsTotal'] = $params['total'];
2143 $relationshipsDT['recordsFiltered'] = $params['total'];
2145 return $relationshipsDT;