(NFC) Update CRM/Contact to match new coder style
[civicrm-core.git] / CRM / Contact / BAO / Relationship.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Class CRM_Contact_BAO_Relationship.
30 */
31 class CRM_Contact_BAO_Relationship extends CRM_Contact_DAO_Relationship {
32
33 /**
34 * Various constants to indicate different type of relationships.
35 *
36 * @var int
37 */
38 const ALL = 0, PAST = 1, DISABLED = 2, CURRENT = 4, INACTIVE = 8;
39
40 /**
41 * Constants for is_permission fields.
42 * Note: the slightly non-obvious ordering is due to history...
43 */
44 const NONE = 0, EDIT = 1, VIEW = 2;
45
46 /**
47 * Create function - use the API instead.
48 *
49 * Note that the previous create function has been renamed 'legacyCreateMultiple'
50 * and this is new in 4.6
51 * All existing calls have been changed to legacyCreateMultiple except the api call - however, it is recommended
52 * that you call that as the end to end testing here is based on the api & refactoring may still be done.
53 *
54 * @param array $params
55 *
56 * @return \CRM_Contact_BAO_Relationship
57 * @throws \CRM_Core_Exception
58 */
59 public static function create(&$params) {
60
61 $extendedParams = self::loadExistingRelationshipDetails($params);
62 // When id is specified we always wan't to update, so we don't need to
63 // check for duplicate relations.
64 if (!isset($params['id']) && self::checkDuplicateRelationship($extendedParams, $extendedParams['contact_id_a'], $extendedParams['contact_id_b'], CRM_Utils_Array::value('id', $extendedParams, 0))) {
65 throw new CRM_Core_Exception('Duplicate Relationship');
66 }
67 $params = $extendedParams;
68 if (self::checkValidRelationship($params, $params, 0)) {
69 throw new CRM_Core_Exception('Invalid Relationship');
70 }
71 $relationship = self::add($params);
72 if (!empty($params['contact_id_a'])) {
73 $ids = array(
74 'contactTarget' => $relationship->contact_id_b,
75 'contact' => $params['contact_id_a'],
76 );
77
78 //CRM-16087 removed additional call to function relatedMemberships which is already called by disableEnableRelationship
79 //resulting in membership being created twice
80 if (array_key_exists('is_active', $params) && empty($params['is_active'])) {
81 $action = CRM_Core_Action::DISABLE;
82 $active = FALSE;
83 }
84 else {
85 $action = CRM_Core_Action::ENABLE;
86 $active = TRUE;
87 }
88 $id = empty($params['id']) ? $relationship->id : $params['id'];
89 self::disableEnableRelationship($id, $action, $params, $ids, $active);
90 }
91
92 if (empty($params['skipRecentView'])) {
93 self::addRecent($params, $relationship);
94 }
95
96 return $relationship;
97 }
98
99 /**
100 * Create multiple relationships for one contact.
101 *
102 * The relationship details are the same for each relationship except the secondary contact
103 * id can be an array.
104 *
105 * @param array $params
106 * Parameters for creating multiple relationships.
107 * The parameters are the same as for relationship create function except that the non-primary
108 * end of the relationship should be an array of one or more contact IDs.
109 * @param string $primaryContactLetter
110 * a or b to denote the primary contact for this action. The secondary may be multiple contacts
111 * and should be an array.
112 *
113 * @return array
114 * @throws \CRM_Core_Exception
115 */
116 public static function createMultiple($params, $primaryContactLetter) {
117 $secondaryContactLetter = ($primaryContactLetter == 'a') ? 'b' : 'a';
118 $secondaryContactIDs = $params['contact_id_' . $secondaryContactLetter];
119 $valid = $invalid = $duplicate = $saved = 0;
120 $relationshipIDs = array();
121 foreach ($secondaryContactIDs as $secondaryContactID) {
122 try {
123 $params['contact_id_' . $secondaryContactLetter] = $secondaryContactID;
124 $relationship = civicrm_api3('relationship', 'create', $params);
125 $relationshipIDs[] = $relationship['id'];
126 $valid++;
127 }
128 catch (CiviCRM_API3_Exception $e) {
129 switch ($e->getMessage()) {
130 case 'Duplicate Relationship':
131 $duplicate++;
132 break;
133
134 case 'Invalid Relationship':
135 $invalid++;
136 break;
137
138 default:
139 throw new CRM_Core_Exception('unknown relationship create error ' . $e->getMessage());
140 }
141 }
142 }
143
144 return array(
145 'valid' => $valid,
146 'invalid' => $invalid,
147 'duplicate' => $duplicate,
148 'saved' => $saved,
149 'relationship_ids' => $relationshipIDs,
150 );
151 }
152
153 /**
154 * Takes an associative array and creates a relationship object.
155 * @deprecated For single creates use the api instead (it's tested).
156 * For multiple a new variant of this function needs to be written and migrated to as this is a bit
157 * nasty
158 *
159 * @param array $params
160 * (reference ) an assoc array of name/value pairs.
161 * @param array $ids
162 * The array that holds all the db ids.
163 * per http://wiki.civicrm.org/confluence/display/CRM/Database+layer
164 * "we are moving away from the $ids param "
165 *
166 * @return CRM_Contact_BAO_Relationship
167 */
168 public static function legacyCreateMultiple(&$params, $ids = array()) {
169 $valid = $invalid = $duplicate = $saved = 0;
170 $relationships = $relationshipIds = array();
171 $relationshipId = CRM_Utils_Array::value('relationship', $ids, CRM_Utils_Array::value('id', $params));
172
173 //CRM-9015 - the hooks are called here & in add (since add doesn't call create)
174 // but in future should be tidied per ticket
175 if (empty($relationshipId)) {
176 $hook = 'create';
177 }
178 else {
179 $hook = 'edit';
180 }
181
182 CRM_Utils_Hook::pre($hook, 'Relationship', $relationshipId, $params);
183
184 if (!$relationshipId) {
185 // creating a new relationship
186 $dataExists = self::dataExists($params);
187 if (!$dataExists) {
188 return array(FALSE, TRUE, FALSE, FALSE, NULL);
189 }
190 $relationshipIds = array();
191 foreach ($params['contact_check'] as $key => $value) {
192 // check if the relationship is valid between contacts.
193 // step 1: check if the relationship is valid if not valid skip and keep the count
194 // step 2: check the if two contacts already have a relationship if yes skip and keep the count
195 // step 3: if valid relationship then add the relation and keep the count
196
197 // step 1
198 $contactFields = self::setContactABFromIDs($params, $ids, $key);
199 $errors = self::checkValidRelationship($contactFields, $ids, $key);
200 if ($errors) {
201 $invalid++;
202 continue;
203 }
204
205 //CRM-16978:check duplicate relationship as per case id.
206 if ($caseId = CRM_Utils_Array::value('case_id', $params)) {
207 $contactFields['case_id'] = $caseId;
208 }
209 if (
210 self::checkDuplicateRelationship(
211 $contactFields,
212 CRM_Utils_Array::value('contact', $ids),
213 // step 2
214 $key
215 )
216 ) {
217 $duplicate++;
218 continue;
219 }
220
221 $singleInstanceParams = array_merge($params, $contactFields);
222 $relationship = self::add($singleInstanceParams);
223 $relationshipIds[] = $relationship->id;
224 $relationships[$relationship->id] = $relationship;
225 $valid++;
226 }
227 // editing the relationship
228 }
229 else {
230 // check for duplicate relationship
231 // @todo this code doesn't cope well with updates - causes e-Notices.
232 // API has a lot of code to work around
233 // this but should review this code & remove the extra handling from the api
234 // it seems doubtful any of this is relevant if the contact fields & relationship
235 // type fields are not set
236 if (
237 self::checkDuplicateRelationship(
238 $params,
239 CRM_Utils_Array::value('contact', $ids),
240 $ids['contactTarget'],
241 $relationshipId
242 )
243 ) {
244 $duplicate++;
245 return array($valid, $invalid, $duplicate, $saved, NULL);
246 }
247
248 $validContacts = TRUE;
249 //validate contacts in update mode also.
250 $contactFields = self::setContactABFromIDs($params, $ids, $ids['contactTarget']);
251 if (!empty($ids['contact']) && !empty($ids['contactTarget'])) {
252 if (self::checkValidRelationship($contactFields, $ids, $ids['contactTarget'])) {
253 $validContacts = FALSE;
254 $invalid++;
255 }
256 }
257 if ($validContacts) {
258 // editing an existing relationship
259 $singleInstanceParams = array_merge($params, $contactFields);
260 $relationship = self::add($singleInstanceParams, $ids, $ids['contactTarget']);
261 $relationshipIds[] = $relationship->id;
262 $relationships[$relationship->id] = $relationship;
263 $saved++;
264 }
265 }
266
267 // do not add to recent items for import, CRM-4399
268 if (!(!empty($params['skipRecentView']) || $invalid || $duplicate)) {
269 self::addRecent($params, $relationship);
270 }
271
272 return array($valid, $invalid, $duplicate, $saved, $relationshipIds, $relationships);
273 }
274
275 /**
276 * This is the function that check/add if the relationship created is valid.
277 *
278 * @param array $params
279 * (reference ) an assoc array of name/value pairs.
280 * @param array $ids
281 * The array that holds all the db ids.
282 * @param int $contactId
283 * This is contact id for adding relationship.
284 *
285 * @return CRM_Contact_BAO_Relationship
286 */
287 public static function add($params, $ids = array(), $contactId = NULL) {
288 $params['id'] = CRM_Utils_Array::value('relationship', $ids, CRM_Utils_Array::value('id', $params));
289
290 $hook = 'create';
291 if ($params['id']) {
292 $hook = 'edit';
293 }
294 //@todo hook are called from create and add - remove one
295 CRM_Utils_Hook::pre($hook, 'Relationship', $params['id'], $params);
296
297 $relationshipTypes = CRM_Utils_Array::value('relationship_type_id', $params);
298
299 // explode the string with _ to get the relationship type id
300 // and to know which contact has to be inserted in
301 // contact_id_a and which one in contact_id_b
302 list($type) = explode('_', $relationshipTypes);
303
304 // check if the relationship type is Head of Household then update the
305 // household's primary contact with this contact.
306 if ($type == 6) {
307 CRM_Contact_BAO_Household::updatePrimaryContact($params['contact_id_b'], $params['contact_id_a']);
308 }
309 if (!empty($params['id']) && self::isCurrentEmployerNeedingToBeCleared($params, $params['id'], $type)) {
310 CRM_Contact_BAO_Contact_Utils::clearCurrentEmployer($params['contact_id_a']);
311 }
312 $relationship = new CRM_Contact_BAO_Relationship();
313 //@todo this code needs to be updated for the possibility that not all fields are set
314 // by using $relationship->copyValues($params);
315 // (update)
316 $relationship->contact_id_b = $params['contact_id_b'];
317 $relationship->contact_id_a = $params['contact_id_a'];
318 $relationship->relationship_type_id = $type;
319 $relationship->id = $params['id'];
320
321 $dateFields = array('end_date', 'start_date');
322
323 foreach (self::getdefaults() as $defaultField => $defaultValue) {
324 if (isset($params[$defaultField])) {
325 if (in_array($defaultField, $dateFields)) {
326 $relationship->$defaultField = CRM_Utils_Date::format(CRM_Utils_Array::value($defaultField, $params));
327 if (!$relationship->$defaultField) {
328 $relationship->$defaultField = 'NULL';
329 }
330 }
331 else {
332 $relationship->$defaultField = $params[$defaultField];
333 }
334 }
335 elseif (empty($params['id'])) {
336 $relationship->$defaultField = $defaultValue;
337 }
338 }
339
340 $relationship->save();
341
342 // add custom field values
343 if (!empty($params['custom'])) {
344 CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_relationship', $relationship->id);
345 }
346
347 $relationship->free();
348
349 CRM_Utils_Hook::post($hook, 'Relationship', $relationship->id, $relationship);
350
351 return $relationship;
352 }
353
354 /**
355 * Add relationship to recent links.
356 *
357 * @param array $params
358 * @param CRM_Contact_DAO_Relationship $relationship
359 */
360 public static function addRecent($params, $relationship) {
361 $url = CRM_Utils_System::url('civicrm/contact/view/rel',
362 "action=view&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&context=home"
363 );
364 $session = CRM_Core_Session::singleton();
365 $recentOther = array();
366 if (($session->get('userID') == $relationship->contact_id_a) ||
367 CRM_Contact_BAO_Contact_Permission::allow($relationship->contact_id_a, CRM_Core_Permission::EDIT)
368 ) {
369 $rType = substr(CRM_Utils_Array::value('relationship_type_id', $params), -3);
370 $recentOther = array(
371 'editUrl' => CRM_Utils_System::url('civicrm/contact/view/rel',
372 "action=update&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home"
373 ),
374 'deleteUrl' => CRM_Utils_System::url('civicrm/contact/view/rel',
375 "action=delete&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home"
376 ),
377 );
378 }
379 $title = CRM_Contact_BAO_Contact::displayName($relationship->contact_id_a) . ' (' . CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType',
380 $relationship->relationship_type_id, 'label_a_b'
381 ) . ' ' . CRM_Contact_BAO_Contact::displayName($relationship->contact_id_b) . ')';
382
383 CRM_Utils_Recent::add($title,
384 $url,
385 $relationship->id,
386 'Relationship',
387 $relationship->contact_id_a,
388 NULL,
389 $recentOther
390 );
391 }
392
393 /**
394 * Load contact ids and relationship type id when doing a create call if not provided.
395 *
396 * There are are various checks done in create which require this information which is optional
397 * when using id.
398 *
399 * @param array $params
400 * Parameters passed to create call.
401 *
402 * @return array
403 * Parameters with missing fields added if required.
404 */
405 public static function loadExistingRelationshipDetails($params) {
406 if (!empty($params['contact_id_a'])
407 && !empty($params['contact_id_b'])
408 && is_numeric($params['relationship_type_id'])) {
409 return $params;
410 }
411 if (empty($params['id'])) {
412 return $params;
413 }
414
415 $fieldsToFill = array('contact_id_a', 'contact_id_b', 'relationship_type_id');
416 $result = CRM_Core_DAO::executeQuery("SELECT " . implode(',', $fieldsToFill) . " FROM civicrm_relationship WHERE id = %1", array(
417 1 => array(
418 $params['id'],
419 'Integer',
420 ),
421 ));
422 while ($result->fetch()) {
423 foreach ($fieldsToFill as $field) {
424 $params[$field] = !empty($params[$field]) ? $params[$field] : $result->$field;
425 }
426 }
427 return $params;
428 }
429
430 /**
431 * Resolve passed in contact IDs to contact_id_a & contact_id_b.
432 *
433 * @param array $params
434 * @param array $ids
435 * @param null $contactID
436 *
437 * @return array
438 * @throws \CRM_Core_Exception
439 */
440 public static function setContactABFromIDs($params, $ids = array(), $contactID = NULL) {
441 $returnFields = array();
442
443 // $ids['contact'] is deprecated but comes from legacyCreateMultiple function.
444 if (empty($ids['contact'])) {
445 if (!empty($params['id'])) {
446 return self::loadExistingRelationshipDetails($params);
447 }
448 throw new CRM_Core_Exception('Cannot create relationship, insufficient contact IDs provided');
449 }
450 if (isset($params['relationship_type_id']) && !is_numeric($params['relationship_type_id'])) {
451 $relationshipTypes = CRM_Utils_Array::value('relationship_type_id', $params);
452 list($relationshipTypeID, $first) = explode('_', $relationshipTypes);
453 $returnFields['relationship_type_id'] = $relationshipTypeID;
454
455 foreach (array('a', 'b') as $contactLetter) {
456 if (empty($params['contact_' . $contactLetter])) {
457 if ($first == $contactLetter) {
458 $returnFields['contact_id_' . $contactLetter] = CRM_Utils_Array::value('contact', $ids);
459 }
460 else {
461 $returnFields['contact_id_' . $contactLetter] = $contactID;
462 }
463 }
464 }
465 }
466
467 return $returnFields;
468 }
469
470 /**
471 * Specify defaults for creating a relationship.
472 *
473 * @return array
474 * array of defaults for creating relationship
475 */
476 public static function getdefaults() {
477 return array(
478 'is_active' => 0,
479 'is_permission_a_b' => self::NONE,
480 'is_permission_b_a' => self::NONE,
481 'description' => '',
482 'start_date' => 'NULL',
483 'case_id' => NULL,
484 'end_date' => 'NULL',
485 );
486 }
487
488 /**
489 * Check if there is data to create the object.
490 *
491 * @param array $params
492 * (reference ) an assoc array of name/value pairs.
493 *
494 * @return bool
495 */
496 public static function dataExists(&$params) {
497 // return if no data present
498 if (!is_array(CRM_Utils_Array::value('contact_check', $params))) {
499 return FALSE;
500 }
501 return TRUE;
502 }
503
504 /**
505 * Get get list of relationship type based on the contact type.
506 *
507 * @param int $contactId
508 * This is the contact id of the current contact.
509 * @param null $contactSuffix
510 * @param string $relationshipId
511 * The id of the existing relationship if any.
512 * @param string $contactType
513 * Contact type.
514 * @param bool $all
515 * If true returns relationship types in both the direction.
516 * @param string $column
517 * Name/label that going to retrieve from db.
518 * @param bool $biDirectional
519 * @param array $contactSubType
520 * Includes relationship types between this subtype.
521 * @param bool $onlySubTypeRelationTypes
522 * If set only subtype which is passed by $contactSubType
523 * related relationship types get return
524 *
525 * @return array
526 * array reference of all relationship types with context to current contact.
527 */
528 public static function getContactRelationshipType(
529 $contactId = NULL,
530 $contactSuffix = NULL,
531 $relationshipId = NULL,
532 $contactType = NULL,
533 $all = FALSE,
534 $column = 'label',
535 $biDirectional = TRUE,
536 $contactSubType = NULL,
537 $onlySubTypeRelationTypes = FALSE
538 ) {
539
540 $relationshipType = array();
541 $allRelationshipType = CRM_Core_PseudoConstant::relationshipType($column);
542
543 $otherContactType = NULL;
544 if ($relationshipId) {
545 $relationship = new CRM_Contact_DAO_Relationship();
546 $relationship->id = $relationshipId;
547 if ($relationship->find(TRUE)) {
548 $contact = new CRM_Contact_DAO_Contact();
549 $contact->id = ($relationship->contact_id_a == $contactId) ? $relationship->contact_id_b : $relationship->contact_id_a;
550
551 if ($contact->find(TRUE)) {
552 $otherContactType = $contact->contact_type;
553 //CRM-5125 for contact subtype specific relationshiptypes
554 if ($contact->contact_sub_type) {
555 $otherContactSubType = $contact->contact_sub_type;
556 }
557 }
558 }
559 }
560
561 $contactSubType = (array) $contactSubType;
562 if ($contactId) {
563 $contactType = CRM_Contact_BAO_Contact::getContactType($contactId);
564 $contactSubType = CRM_Contact_BAO_Contact::getContactSubType($contactId);
565 }
566
567 foreach ($allRelationshipType as $key => $value) {
568 // the contact type is required or matches
569 if (((!$value['contact_type_a']) ||
570 $value['contact_type_a'] == $contactType
571 ) &&
572 // the other contact type is required or present or matches
573 ((!$value['contact_type_b']) ||
574 (!$otherContactType) ||
575 $value['contact_type_b'] == $otherContactType
576 ) &&
577 (in_array($value['contact_sub_type_a'], $contactSubType) ||
578 (!$value['contact_sub_type_a'] && !$onlySubTypeRelationTypes)
579 )
580 ) {
581 $relationshipType[$key . '_a_b'] = $value["{$column}_a_b"];
582 }
583
584 if (((!$value['contact_type_b']) ||
585 $value['contact_type_b'] == $contactType
586 ) &&
587 ((!$value['contact_type_a']) ||
588 (!$otherContactType) ||
589 $value['contact_type_a'] == $otherContactType
590 ) &&
591 (in_array($value['contact_sub_type_b'], $contactSubType) ||
592 (!$value['contact_sub_type_b'] && !$onlySubTypeRelationTypes)
593 )
594 ) {
595 $relationshipType[$key . '_b_a'] = $value["{$column}_b_a"];
596 }
597
598 if ($all) {
599 $relationshipType[$key . '_a_b'] = $value["{$column}_a_b"];
600 $relationshipType[$key . '_b_a'] = $value["{$column}_b_a"];
601 }
602 }
603
604 if ($biDirectional) {
605 $relationshipType = self::removeRelationshipTypeDuplicates($relationshipType, $contactSuffix);
606 }
607
608 // sort the relationshipType in ascending order CRM-7736
609 asort($relationshipType);
610 return $relationshipType;
611 }
612
613 /**
614 * Given a list of relationship types, return the list with duplicate types
615 * removed, being careful to retain only the duplicate which matches the given
616 * 'a_b' or 'b_a' suffix.
617 *
618 * @param array $relationshipTypeList A list of relationship types, in the format
619 * returned by self::getContactRelationshipType().
620 * @param string $suffix Either 'a_b' or 'b_a'; defaults to 'a_b'
621 *
622 * @return array The modified value of $relationshipType
623 */
624 public static function removeRelationshipTypeDuplicates($relationshipTypeList, $suffix = NULL) {
625 if (empty($suffix)) {
626 $suffix = 'a_b';
627 }
628
629 // Find those labels which are listed more than once.
630 $duplicateValues = array_diff_assoc($relationshipTypeList, array_unique($relationshipTypeList));
631
632 // For each duplicate label, find its keys, and remove from $relationshipType
633 // the key which does not match $suffix.
634 foreach ($duplicateValues as $value) {
635 $keys = array_keys($relationshipTypeList, $value);
636 foreach ($keys as $key) {
637 if (substr($key, -3) != $suffix) {
638 unset($relationshipTypeList[$key]);
639 }
640 }
641 }
642 return $relationshipTypeList;
643 }
644
645 /**
646 * Delete current employer relationship.
647 *
648 * @param int $id
649 * @param int $action
650 *
651 * @return CRM_Contact_DAO_Relationship
652 */
653 public static function clearCurrentEmployer($id, $action) {
654 $relationship = new CRM_Contact_DAO_Relationship();
655 $relationship->id = $id;
656 $relationship->find(TRUE);
657
658 //to delete relationship between household and individual \
659 //or between individual and organization
660 if (($action & CRM_Core_Action::DISABLE) || ($action & CRM_Core_Action::DELETE)) {
661 $relTypes = CRM_Utils_Array::index(array('name_a_b'), CRM_Core_PseudoConstant::relationshipType('name'));
662 if (
663 (isset($relTypes['Employee of']) && $relationship->relationship_type_id == $relTypes['Employee of']['id']) ||
664 (isset($relTypes['Household Member of']) && $relationship->relationship_type_id == $relTypes['Household Member of']['id'])
665 ) {
666 $sharedContact = new CRM_Contact_DAO_Contact();
667 $sharedContact->id = $relationship->contact_id_a;
668 $sharedContact->find(TRUE);
669
670 // CRM-15881 UPDATES
671 // changed FROM "...relationship->relationship_type_id == 4..." TO "...relationship->relationship_type_id == 5..."
672 // As the system should be looking for type "employer of" (id 5) and not "sibling of" (id 4)
673 // 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.
674 $employerRelTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
675
676 if ($relationship->relationship_type_id == $employerRelTypeId && $relationship->contact_id_b == $sharedContact->employer_id) {
677 CRM_Contact_BAO_Contact_Utils::clearCurrentEmployer($relationship->contact_id_a);
678 }
679
680 }
681 }
682 return $relationship;
683 }
684
685 /**
686 * Delete the relationship.
687 *
688 * @param int $id
689 * Relationship id.
690 *
691 * @return null
692 */
693 public static function del($id) {
694 // delete from relationship table
695 CRM_Utils_Hook::pre('delete', 'Relationship', $id, CRM_Core_DAO::$_nullArray);
696
697 $relationship = self::clearCurrentEmployer($id, CRM_Core_Action::DELETE);
698 if (CRM_Core_Permission::access('CiviMember')) {
699 // create $params array which isrequired to delete memberships
700 // of the related contacts.
701 $params = array(
702 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b",
703 'contact_check' => array($relationship->contact_id_b => 1),
704 );
705
706 $ids = array();
707 // calling relatedMemberships to delete the memberships of
708 // related contacts.
709 self::relatedMemberships($relationship->contact_id_a,
710 $params,
711 $ids,
712 CRM_Core_Action::DELETE,
713 FALSE
714 );
715 }
716
717 $relationship->delete();
718 CRM_Core_Session::setStatus(ts('Selected relationship has been deleted successfully.'), ts('Record Deleted'), 'success');
719
720 CRM_Utils_Hook::post('delete', 'Relationship', $id, $relationship);
721
722 // delete the recently created Relationship
723 $relationshipRecent = array(
724 'id' => $id,
725 'type' => 'Relationship',
726 );
727 CRM_Utils_Recent::del($relationshipRecent);
728
729 return $relationship;
730 }
731
732 /**
733 * Disable/enable the relationship.
734 *
735 * @param int $id
736 * Relationship id.
737 *
738 * @param int $action
739 * @param array $params
740 * @param array $ids
741 * @param bool $active
742 */
743 public static function disableEnableRelationship($id, $action, $params = array(), $ids = array(), $active = FALSE) {
744 $relationship = self::clearCurrentEmployer($id, $action);
745
746 if ($id) {
747 // create $params array which is required to delete memberships
748 // of the related contacts.
749 if (empty($params)) {
750 $params = array(
751 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b",
752 'contact_check' => array($relationship->contact_id_b => 1),
753 );
754 }
755 $contact_id_a = empty($params['contact_id_a']) ? $relationship->contact_id_a : $params['contact_id_a'];
756 // calling relatedMemberships to delete/add the memberships of
757 // related contacts.
758 if ($action & CRM_Core_Action::DISABLE) {
759 CRM_Contact_BAO_Relationship::relatedMemberships($contact_id_a,
760 $params,
761 $ids,
762 CRM_Core_Action::DELETE,
763 $active
764 );
765 }
766 elseif ($action & CRM_Core_Action::ENABLE) {
767 $ids['contact'] = empty($ids['contact']) ? $contact_id_a : $ids['contact'];
768 CRM_Contact_BAO_Relationship::relatedMemberships($contact_id_a,
769 $params,
770 $ids,
771 empty($params['id']) ? CRM_Core_Action::ADD : CRM_Core_Action::UPDATE,
772 $active
773 );
774 }
775 }
776 }
777
778 /**
779 * Delete the object records that are associated with this contact.
780 *
781 * @param int $contactId
782 * Id of the contact to delete.
783 */
784 public static function deleteContact($contactId) {
785 $relationship = new CRM_Contact_DAO_Relationship();
786 $relationship->contact_id_a = $contactId;
787 $relationship->delete();
788
789 $relationship = new CRM_Contact_DAO_Relationship();
790 $relationship->contact_id_b = $contactId;
791 $relationship->delete();
792
793 CRM_Contact_BAO_Household::updatePrimaryContact(NULL, $contactId);
794 }
795
796 /**
797 * Get the other contact in a relationship.
798 *
799 * @param int $id
800 * Relationship id.
801 *
802 * $returns returns the contact ids in the realtionship
803 *
804 * @return \CRM_Contact_DAO_Relationship
805 */
806 public static function getRelationshipByID($id) {
807 $relationship = new CRM_Contact_DAO_Relationship();
808
809 $relationship->id = $id;
810 $relationship->selectAdd();
811 $relationship->selectAdd('contact_id_a, contact_id_b');
812 $relationship->find(TRUE);
813
814 return $relationship;
815 }
816
817 /**
818 * Check if the relationship type selected between two contacts is correct.
819 *
820 * @param int $contact_a
821 * 1st contact id.
822 * @param int $contact_b
823 * 2nd contact id.
824 * @param int $relationshipTypeId
825 * Relationship type id.
826 *
827 * @return bool
828 * true if it is valid relationship else false
829 */
830 public static function checkRelationshipType($contact_a, $contact_b, $relationshipTypeId) {
831 $relationshipType = new CRM_Contact_DAO_RelationshipType();
832 $relationshipType->id = $relationshipTypeId;
833 $relationshipType->selectAdd();
834 $relationshipType->selectAdd('contact_type_a, contact_type_b, contact_sub_type_a, contact_sub_type_b');
835 if ($relationshipType->find(TRUE)) {
836 $contact_type_a = CRM_Contact_BAO_Contact::getContactType($contact_a);
837 $contact_type_b = CRM_Contact_BAO_Contact::getContactType($contact_b);
838
839 $contact_sub_type_a = CRM_Contact_BAO_Contact::getContactSubType($contact_a);
840 $contact_sub_type_b = CRM_Contact_BAO_Contact::getContactSubType($contact_b);
841
842 if (((!$relationshipType->contact_type_a) || ($relationshipType->contact_type_a == $contact_type_a)) &&
843 ((!$relationshipType->contact_type_b) || ($relationshipType->contact_type_b == $contact_type_b)) &&
844 ((!$relationshipType->contact_sub_type_a) || (in_array($relationshipType->contact_sub_type_a,
845 $contact_sub_type_a
846 ))) &&
847 ((!$relationshipType->contact_sub_type_b) || (in_array($relationshipType->contact_sub_type_b,
848 $contact_sub_type_b
849 )))
850 ) {
851 return TRUE;
852 }
853 else {
854 return FALSE;
855 }
856 }
857 return FALSE;
858 }
859
860 /**
861 * This function does the validtion for valid relationship.
862 *
863 * @param array $params
864 * This array contains the values there are subitted by the form.
865 * @param array $ids
866 * The array that holds all the db ids.
867 * @param int $contactId
868 * This is contact id for adding relationship.
869 *
870 * @return string
871 */
872 public static function checkValidRelationship($params, $ids, $contactId) {
873 $errors = '';
874 // function to check if the relationship selected is correct
875 // i.e. employer relationship can exit between Individual and Organization (not between Individual and Individual)
876 if (!CRM_Contact_BAO_Relationship::checkRelationshipType($params['contact_id_a'], $params['contact_id_b'],
877 $params['relationship_type_id'])) {
878 $errors = 'Please select valid relationship between these two contacts.';
879 }
880 return $errors;
881 }
882
883 /**
884 * This function checks for duplicate relationship.
885 *
886 * @param array $params
887 * (reference ) an assoc array of name/value pairs.
888 * @param int $id
889 * This the id of the contact whom we are adding relationship.
890 * @param int $contactId
891 * This is contact id for adding relationship.
892 * @param int $relationshipId
893 * This is relationship id for the contact.
894 *
895 * @return bool
896 * true if record exists else false
897 */
898 public static function checkDuplicateRelationship(&$params, $id, $contactId = 0, $relationshipId = 0) {
899 $relationshipTypeId = CRM_Utils_Array::value('relationship_type_id', $params);
900 list($type) = explode('_', $relationshipTypeId);
901
902 $queryString = "
903 SELECT id
904 FROM civicrm_relationship
905 WHERE relationship_type_id = " . CRM_Utils_Type::escape($type, 'Integer');
906
907 /*
908 * CRM-11792 - date fields from API are in ISO format, but this function
909 * supports date arrays BAO has increasingly standardised to ISO format
910 * so I believe this function should support ISO rather than make API
911 * format it - however, need to support array format for now to avoid breakage
912 * @ time of writing this function is called from Relationship::legacyCreateMultiple (twice)
913 * CRM_BAO_Contact_Utils::clearCurrentEmployer (seemingly without dates)
914 * CRM_Contact_Form_Task_AddToOrganization::postProcess &
915 * CRM_Contact_Form_Task_AddToHousehold::postProcess
916 * (I don't think the last 2 support dates but not sure
917 */
918
919 $dateFields = array('end_date', 'start_date');
920 foreach ($dateFields as $dateField) {
921 if (array_key_exists($dateField, $params)) {
922 if (empty($params[$dateField]) || $params[$dateField] == 'null') {
923 //this is most likely coming from an api call & probably loaded
924 // from the DB to deal with some of the
925 // other myriad of excessive checks still in place both in
926 // the api & the create functions
927 $queryString .= " AND $dateField IS NULL";
928 continue;
929 }
930 elseif (is_array($params[$dateField])) {
931 $queryString .= " AND $dateField = " .
932 CRM_Utils_Type::escape(CRM_Utils_Date::format($params[$dateField]), 'Date');
933 }
934 else {
935 $queryString .= " AND $dateField = " .
936 CRM_Utils_Type::escape($params[$dateField], 'Date');
937 }
938 }
939 }
940
941 $queryString .=
942 " AND ( ( contact_id_a = " . CRM_Utils_Type::escape($id, 'Integer') .
943 " AND contact_id_b = " . CRM_Utils_Type::escape($contactId, 'Integer') .
944 " ) OR ( contact_id_a = " . CRM_Utils_Type::escape($contactId, 'Integer') .
945 " AND contact_id_b = " . CRM_Utils_Type::escape($id, 'Integer') . " ) ) ";
946
947 //if caseId is provided, include it duplicate checking.
948 if ($caseId = CRM_Utils_Array::value('case_id', $params)) {
949 $queryString .= " AND case_id = " . CRM_Utils_Type::escape($caseId, 'Integer');
950 }
951
952 if ($relationshipId) {
953 $queryString .= " AND id !=" . CRM_Utils_Type::escape($relationshipId, 'Integer');
954 }
955
956 $relationship = new CRM_Contact_BAO_Relationship();
957 $relationship->query($queryString);
958 while ($relationship->fetch()) {
959 // Check whether the custom field values are identical.
960 $result = self::checkDuplicateCustomFields($params, $relationship->id);
961 if ($result) {
962 $relationship->free();
963 return TRUE;
964 }
965 }
966 $relationship->free();
967 return FALSE;
968 }
969
970 /**
971 * this function checks whether the values of the custom fields in $params are
972 * the same as the values of the custom fields of the relation with given
973 * $relationshipId.
974 *
975 * @param array $params (reference) an assoc array of name/value pairs
976 * @param int $relationshipId ID of an existing duplicate relation
977 *
978 * @return boolean true if custom field values are identical
979 * @access private
980 * @static
981 */
982 private static function checkDuplicateCustomFields(&$params, $relationshipId) {
983 // Get the custom values of the existing relationship.
984 $existingValues = CRM_Core_BAO_CustomValueTable::getEntityValues($relationshipId, 'Relationship');
985 // Create a similar array for the new relationship.
986 $newValues = array();
987 if (array_key_exists('custom', $params)) {
988 // $params['custom'] seems to be an array. Each value is again an array.
989 // This array contains one value (key -1), and this value seems to be
990 // an array with the information about the custom value.
991 foreach ($params['custom'] as $value) {
992 foreach ($value as $customValue) {
993 $newValues[$customValue['custom_field_id']] = $customValue['value'];
994 }
995 }
996 }
997
998 // Calculate difference between arrays. If the only key-value pairs
999 // that are in one array but not in the other are empty, the
1000 // custom fields are considered to be equal.
1001 // See https://github.com/civicrm/civicrm-core/pull/6515#issuecomment-137985667
1002 $diff1 = array_diff_assoc($existingValues, $newValues);
1003 $diff2 = array_diff_assoc($newValues, $existingValues);
1004
1005 return !array_filter($diff1) && !array_filter($diff2);
1006 }
1007
1008 /**
1009 * Update the is_active flag in the db.
1010 *
1011 * @param int $id
1012 * Id of the database record.
1013 * @param bool $is_active
1014 * Value we want to set the is_active field.
1015 *
1016 * @throws CiviCRM_API3_Exception
1017 * @return Object
1018 * DAO object on success, null otherwise
1019 */
1020 public static function setIsActive($id, $is_active) {
1021 // as both the create & add functions have a bunch of logic in them that
1022 // doesn't seem to cope with a normal update we will call the api which
1023 // has tested handling for this
1024 // however, a longer term solution would be to simplify the add, create & api functions
1025 // to be more standard. It is debatable @ that point whether it's better to call the BAO
1026 // direct as the api is more tested.
1027 $result = civicrm_api('relationship', 'create', array(
1028 'id' => $id,
1029 'is_active' => $is_active,
1030 'version' => 3,
1031 ));
1032
1033 if (is_array($result) && !empty($result['is_error']) && $result['error_message'] != 'Duplicate Relationship') {
1034 throw new CiviCRM_API3_Exception($result['error_message'], CRM_Utils_Array::value('error_code', $result, 'undefined'), $result);
1035 }
1036
1037 return TRUE;
1038 }
1039
1040 /**
1041 * Fetch a relationship object and store the values in the values array.
1042 *
1043 * @param array $params
1044 * Input parameters to find object.
1045 * @param array $values
1046 * Output values of the object.
1047 *
1048 * @return array
1049 * (reference) the values that could be potentially assigned to smarty
1050 */
1051 public static function &getValues(&$params, &$values) {
1052 if (empty($params)) {
1053 return NULL;
1054 }
1055 $v = array();
1056
1057 // get the specific number of relationship or all relationships.
1058 if (!empty($params['numRelationship'])) {
1059 $v['data'] = &CRM_Contact_BAO_Relationship::getRelationship($params['contact_id'], NULL, $params['numRelationship']);
1060 }
1061 else {
1062 $v['data'] = CRM_Contact_BAO_Relationship::getRelationship($params['contact_id']);
1063 }
1064
1065 // get the total count of relationships
1066 $v['totalCount'] = count($v['data']);
1067
1068 $values['relationship']['data'] = &$v['data'];
1069 $values['relationship']['totalCount'] = &$v['totalCount'];
1070
1071 return $v;
1072 }
1073
1074 /**
1075 * Helper function to form the sql for relationship retrieval.
1076 *
1077 * @param int $contactId
1078 * Contact id.
1079 * @param int $status
1080 * (check const at top of file).
1081 * @param int $numRelationship
1082 * No of relationships to display (limit).
1083 * @param int $count
1084 * Get the no of relationships.
1085 * $param int $relationshipId relationship id
1086 * @param int $relationshipId
1087 * @param string $direction
1088 * The direction we are interested in a_b or b_a.
1089 * @param array $params
1090 * Array of extra values including relationship_type_id per api spec.
1091 *
1092 * @return array
1093 * [select, from, where]
1094 */
1095 public static function makeURLClause($contactId, $status, $numRelationship, $count, $relationshipId, $direction, $params = array()) {
1096 $select = $from = $where = '';
1097
1098 $select = '( ';
1099 if ($count) {
1100 if ($direction == 'a_b') {
1101 $select .= ' SELECT count(DISTINCT civicrm_relationship.id) as cnt1, 0 as cnt2 ';
1102 }
1103 else {
1104 $select .= ' SELECT 0 as cnt1, count(DISTINCT civicrm_relationship.id) as cnt2 ';
1105 }
1106 }
1107 else {
1108 $select .= ' SELECT civicrm_relationship.id as civicrm_relationship_id,
1109 civicrm_contact.sort_name as sort_name,
1110 civicrm_contact.display_name as display_name,
1111 civicrm_contact.job_title as job_title,
1112 civicrm_contact.employer_id as employer_id,
1113 civicrm_contact.organization_name as organization_name,
1114 civicrm_address.street_address as street_address,
1115 civicrm_address.city as city,
1116 civicrm_address.postal_code as postal_code,
1117 civicrm_state_province.abbreviation as state,
1118 civicrm_country.name as country,
1119 civicrm_email.email as email,
1120 civicrm_contact.contact_type as contact_type,
1121 civicrm_contact.contact_sub_type as contact_sub_type,
1122 civicrm_phone.phone as phone,
1123 civicrm_contact.id as civicrm_contact_id,
1124 civicrm_relationship.contact_id_b as contact_id_b,
1125 civicrm_relationship.contact_id_a as contact_id_a,
1126 civicrm_relationship_type.id as civicrm_relationship_type_id,
1127 civicrm_relationship.start_date as start_date,
1128 civicrm_relationship.end_date as end_date,
1129 civicrm_relationship.description as description,
1130 civicrm_relationship.is_active as is_active,
1131 civicrm_relationship.is_permission_a_b as is_permission_a_b,
1132 civicrm_relationship.is_permission_b_a as is_permission_b_a,
1133 civicrm_relationship.case_id as case_id';
1134
1135 if ($direction == 'a_b') {
1136 $select .= ', civicrm_relationship_type.label_a_b as label_a_b,
1137 civicrm_relationship_type.label_b_a as relation ';
1138 }
1139 else {
1140 $select .= ', civicrm_relationship_type.label_a_b as label_a_b,
1141 civicrm_relationship_type.label_a_b as relation ';
1142 }
1143 }
1144
1145 $from = "
1146 FROM civicrm_relationship
1147 INNER JOIN civicrm_relationship_type ON ( civicrm_relationship.relationship_type_id = civicrm_relationship_type.id )
1148 INNER JOIN civicrm_contact ";
1149 if ($direction == 'a_b') {
1150 $from .= 'ON ( civicrm_contact.id = civicrm_relationship.contact_id_a ) ';
1151 }
1152 else {
1153 $from .= 'ON ( civicrm_contact.id = civicrm_relationship.contact_id_b ) ';
1154 }
1155
1156 if (!$count) {
1157 $from .= "
1158 LEFT JOIN civicrm_address ON (civicrm_address.contact_id = civicrm_contact.id AND civicrm_address.is_primary = 1)
1159 LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = civicrm_contact.id AND civicrm_phone.is_primary = 1)
1160 LEFT JOIN civicrm_email ON (civicrm_email.contact_id = civicrm_contact.id AND civicrm_email.is_primary = 1)
1161 LEFT JOIN civicrm_state_province ON (civicrm_address.state_province_id = civicrm_state_province.id)
1162 LEFT JOIN civicrm_country ON (civicrm_address.country_id = civicrm_country.id)
1163 ";
1164 }
1165
1166 $where = 'WHERE ( 1 )';
1167 if ($contactId) {
1168 if ($direction == 'a_b') {
1169 $where .= ' AND civicrm_relationship.contact_id_b = ' . CRM_Utils_Type::escape($contactId, 'Positive');
1170 }
1171 else {
1172 $where .= ' AND civicrm_relationship.contact_id_a = ' . CRM_Utils_Type::escape($contactId, 'Positive') . '
1173 AND civicrm_relationship.contact_id_a != civicrm_relationship.contact_id_b ';
1174 }
1175 }
1176 if ($relationshipId) {
1177 $where .= ' AND civicrm_relationship.id = ' . CRM_Utils_Type::escape($relationshipId, 'Positive');
1178 }
1179
1180 $date = date('Y-m-d');
1181 if ($status == self::PAST) {
1182 //this case for showing past relationship
1183 $where .= ' AND civicrm_relationship.is_active = 1 ';
1184 $where .= " AND civicrm_relationship.end_date < '" . $date . "'";
1185 }
1186 elseif ($status == self::DISABLED) {
1187 // this case for showing disabled relationship
1188 $where .= ' AND civicrm_relationship.is_active = 0 ';
1189 }
1190 elseif ($status == self::CURRENT) {
1191 //this case for showing current relationship
1192 $where .= ' AND civicrm_relationship.is_active = 1 ';
1193 $where .= " AND (civicrm_relationship.end_date >= '" . $date . "' OR civicrm_relationship.end_date IS NULL) ";
1194 }
1195 elseif ($status == self::INACTIVE) {
1196 //this case for showing inactive relationships
1197 $where .= " AND (civicrm_relationship.end_date < '" . $date . "'";
1198 $where .= ' OR civicrm_relationship.is_active = 0 )';
1199 }
1200
1201 // CRM-6181
1202 $where .= ' AND civicrm_contact.is_deleted = 0';
1203 if (!empty($params['membership_type_id']) && empty($params['relationship_type_id'])) {
1204 $where .= self::membershipTypeToRelationshipTypes($params, $direction);
1205 }
1206 if (!empty($params['relationship_type_id'])) {
1207 if (is_array($params['relationship_type_id'])) {
1208 $where .= " AND " . CRM_Core_DAO::createSQLFilter('relationship_type_id', $params['relationship_type_id'], 'Integer');
1209 }
1210 else {
1211 $where .= ' AND relationship_type_id = ' . CRM_Utils_Type::escape($params['relationship_type_id'], 'Positive');
1212 }
1213 }
1214 if ($direction == 'a_b') {
1215 $where .= ' ) UNION ';
1216 }
1217 else {
1218 $where .= ' ) ';
1219 }
1220
1221 return array($select, $from, $where);
1222 }
1223
1224 /**
1225 * Get a list of relationships.
1226 *
1227 * @param int $contactId
1228 * Contact id.
1229 * @param int $status
1230 * 1: Past 2: Disabled 3: Current.
1231 * @param int $numRelationship
1232 * No of relationships to display (limit).
1233 * @param int $count
1234 * Get the no of relationships.
1235 * @param int $relationshipId
1236 * @param array $links
1237 * the list of links to display
1238 * @param int $permissionMask
1239 * the permission mask to be applied for the actions
1240 * @param bool $permissionedContact
1241 * to return only permissioned Contact
1242 * @param array $params
1243 * @param bool $includeTotalCount
1244 * Should we return a count of total accessable relationships
1245 *
1246 * @return array|int
1247 * relationship records
1248 */
1249 public static function getRelationship(
1250 $contactId = NULL,
1251 $status = 0, $numRelationship = 0,
1252 $count = 0, $relationshipId = 0,
1253 $links = NULL, $permissionMask = NULL,
1254 $permissionedContact = FALSE,
1255 $params = array(), $includeTotalCount = FALSE
1256 ) {
1257 $values = array();
1258 if (!$contactId && !$relationshipId) {
1259 return $values;
1260 }
1261
1262 list($select1, $from1, $where1) = self::makeURLClause($contactId, $status, $numRelationship,
1263 $count, $relationshipId, 'a_b', $params
1264 );
1265 list($select2, $from2, $where2) = self::makeURLClause($contactId, $status, $numRelationship,
1266 $count, $relationshipId, 'b_a', $params
1267 );
1268
1269 $order = $limit = '';
1270 if (!$count) {
1271 if (empty($params['sort'])) {
1272 $order = ' ORDER BY civicrm_relationship_type_id, sort_name ';
1273 }
1274 else {
1275 $order = " ORDER BY {$params['sort']} ";
1276 }
1277
1278 $offset = 0;
1279 if (!empty($params['offset']) && $params['offset'] > 0) {
1280 $offset = $params['offset'];
1281 }
1282
1283 if ($numRelationship) {
1284 $limit = " LIMIT {$offset}, $numRelationship";
1285 }
1286 }
1287
1288 // building the query string
1289 $queryString = $select1 . $from1 . $where1 . $select2 . $from2 . $where2;
1290
1291 $relationship = new CRM_Contact_DAO_Relationship();
1292
1293 $relationship->query($queryString . $order . $limit);
1294 $row = array();
1295 if ($count) {
1296 $relationshipCount = 0;
1297 while ($relationship->fetch()) {
1298 $relationshipCount += $relationship->cnt1 + $relationship->cnt2;
1299 }
1300 return $relationshipCount;
1301 }
1302 else {
1303
1304 if ($includeTotalCount) {
1305 $values['total_relationships'] = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM ({$queryString}) AS r");
1306 }
1307
1308 $mask = NULL;
1309 if ($status != self::INACTIVE) {
1310 if ($links) {
1311 $mask = array_sum(array_keys($links));
1312 if ($mask & CRM_Core_Action::DISABLE) {
1313 $mask -= CRM_Core_Action::DISABLE;
1314 }
1315 if ($mask & CRM_Core_Action::ENABLE) {
1316 $mask -= CRM_Core_Action::ENABLE;
1317 }
1318
1319 if ($status == self::CURRENT) {
1320 $mask |= CRM_Core_Action::DISABLE;
1321 }
1322 elseif ($status == self::DISABLED) {
1323 $mask |= CRM_Core_Action::ENABLE;
1324 }
1325 }
1326 // temporary hold the value of $mask.
1327 $tempMask = $mask;
1328 }
1329
1330 while ($relationship->fetch()) {
1331 $rid = $relationship->civicrm_relationship_id;
1332 $cid = $relationship->civicrm_contact_id;
1333
1334 if ($permissionedContact &&
1335 (!CRM_Contact_BAO_Contact_Permission::allow($cid))
1336 ) {
1337 continue;
1338 }
1339 if ($status != self::INACTIVE && $links) {
1340 // assign the original value to $mask
1341 $mask = $tempMask;
1342 // display action links if $cid has edit permission for the relationship.
1343 if (!($permissionMask & CRM_Core_Permission::EDIT) && CRM_Contact_BAO_Contact_Permission::allow($cid, CRM_Core_Permission::EDIT)) {
1344 $permissions[] = CRM_Core_Permission::EDIT;
1345 $permissions[] = CRM_Core_Permission::DELETE;
1346 $permissionMask = CRM_Core_Action::mask($permissions);
1347 }
1348 $mask = $mask & $permissionMask;
1349 }
1350 $values[$rid]['id'] = $rid;
1351 $values[$rid]['cid'] = $cid;
1352 $values[$rid]['contact_id_a'] = $relationship->contact_id_a;
1353 $values[$rid]['contact_id_b'] = $relationship->contact_id_b;
1354 $values[$rid]['contact_type'] = $relationship->contact_type;
1355 $values[$rid]['contact_sub_type'] = $relationship->contact_sub_type;
1356 $values[$rid]['relationship_type_id'] = $relationship->civicrm_relationship_type_id;
1357 $values[$rid]['relation'] = $relationship->relation;
1358 $values[$rid]['name'] = $relationship->sort_name;
1359 $values[$rid]['display_name'] = $relationship->display_name;
1360 $values[$rid]['job_title'] = $relationship->job_title;
1361 $values[$rid]['email'] = $relationship->email;
1362 $values[$rid]['phone'] = $relationship->phone;
1363 $values[$rid]['employer_id'] = $relationship->employer_id;
1364 $values[$rid]['organization_name'] = $relationship->organization_name;
1365 $values[$rid]['country'] = $relationship->country;
1366 $values[$rid]['city'] = $relationship->city;
1367 $values[$rid]['state'] = $relationship->state;
1368 $values[$rid]['start_date'] = $relationship->start_date;
1369 $values[$rid]['end_date'] = $relationship->end_date;
1370 $values[$rid]['description'] = $relationship->description;
1371 $values[$rid]['is_active'] = $relationship->is_active;
1372 $values[$rid]['is_permission_a_b'] = $relationship->is_permission_a_b;
1373 $values[$rid]['is_permission_b_a'] = $relationship->is_permission_b_a;
1374 $values[$rid]['case_id'] = $relationship->case_id;
1375
1376 if ($status) {
1377 $values[$rid]['status'] = $status;
1378 }
1379
1380 $values[$rid]['civicrm_relationship_type_id'] = $relationship->civicrm_relationship_type_id;
1381
1382 if ($relationship->contact_id_a == $contactId) {
1383 $values[$rid]['rtype'] = 'a_b';
1384 }
1385 else {
1386 $values[$rid]['rtype'] = 'b_a';
1387 }
1388
1389 if ($links) {
1390 $replace = array(
1391 'id' => $rid,
1392 'rtype' => $values[$rid]['rtype'],
1393 'cid' => $contactId,
1394 'cbid' => $values[$rid]['cid'],
1395 'caseid' => $values[$rid]['case_id'],
1396 'clientid' => $contactId,
1397 );
1398
1399 if ($status == self::INACTIVE) {
1400 // setting links for inactive relationships
1401 $mask = array_sum(array_keys($links));
1402 if (!$values[$rid]['is_active']) {
1403 $mask -= CRM_Core_Action::DISABLE;
1404 }
1405 else {
1406 $mask -= CRM_Core_Action::ENABLE;
1407 $mask -= CRM_Core_Action::DISABLE;
1408 }
1409 $mask = $mask & $permissionMask;
1410 }
1411
1412 // Give access to manage case link by copying to MAX_ACTION index temporarily, depending on case permission of user.
1413 if ($values[$rid]['case_id']) {
1414 // Borrowed logic from CRM_Case_Page_Tab
1415 $hasCaseAccess = FALSE;
1416 if (CRM_Core_Permission::check('access all cases and activities')) {
1417 $hasCaseAccess = TRUE;
1418 }
1419 else {
1420 $userCases = CRM_Case_BAO_Case::getCases(FALSE);
1421 if (array_key_exists($values[$rid]['case_id'], $userCases)) {
1422 $hasCaseAccess = TRUE;
1423 }
1424 }
1425
1426 if ($hasCaseAccess) {
1427 // give access by copying to MAX_ACTION temporarily, otherwise leave at NONE which won't display
1428 $links[CRM_Core_Action::MAX_ACTION] = $links[CRM_Core_Action::NONE];
1429 $links[CRM_Core_Action::MAX_ACTION]['name'] = ts('Manage Case #%1', array(1 => $values[$rid]['case_id']));
1430 $links[CRM_Core_Action::MAX_ACTION]['class'] = 'no-popup';
1431
1432 // Also make sure we have the right client cid since can get here from multiple relationship tabs.
1433 if ($values[$rid]['rtype'] == 'b_a') {
1434 $replace['clientid'] = $values[$rid]['cid'];
1435 }
1436 $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>';
1437 }
1438 }
1439
1440 $values[$rid]['action'] = CRM_Core_Action::formLink(
1441 $links,
1442 $mask,
1443 $replace,
1444 ts('more'),
1445 FALSE,
1446 'relationship.selector.row',
1447 'Relationship',
1448 $rid);
1449 unset($links[CRM_Core_Action::MAX_ACTION]);
1450 }
1451 }
1452
1453 $relationship->free();
1454 return $values;
1455 }
1456 }
1457
1458 /**
1459 * Get get list of relationship type based on the target contact type.
1460 *
1461 * @param string $targetContactType
1462 * It's valid contact tpye(may be Individual , Organization , Household).
1463 *
1464 * @return array
1465 * array reference of all relationship types with context to current contact type .
1466 */
1467 public static function getRelationType($targetContactType) {
1468 $relationshipType = array();
1469 $allRelationshipType = CRM_Core_PseudoConstant::relationshipType();
1470
1471 foreach ($allRelationshipType as $key => $type) {
1472 if ($type['contact_type_b'] == $targetContactType || empty($type['contact_type_b'])) {
1473 $relationshipType[$key . '_a_b'] = $type['label_a_b'];
1474 }
1475 }
1476
1477 return $relationshipType;
1478 }
1479
1480 /**
1481 * Create / update / delete membership for related contacts.
1482 *
1483 * This function will create/update/delete membership for related
1484 * contact based on 1) contact have active membership 2) that
1485 * membership is is extedned by the same relationship type to that
1486 * of the existing relationship.
1487 *
1488 * @param int $contactId
1489 * contact id.
1490 * @param array $params
1491 * array of values submitted by POST.
1492 * @param array $ids
1493 * array of ids.
1494 * @param \const|int $action which action called this function
1495 *
1496 * @param bool $active
1497 *
1498 * @throws \CRM_Core_Exception
1499 */
1500 public static function relatedMemberships($contactId, &$params, $ids, $action = CRM_Core_Action::ADD, $active = TRUE) {
1501 // Check the end date and set the status of the relationship
1502 // accordingly.
1503 $status = self::CURRENT;
1504 $targetContact = $targetContact = CRM_Utils_Array::value('contact_check', $params, array());
1505 $today = date('Ymd');
1506
1507 // If a relationship hasn't yet started, just return for now
1508 // TODO: handle edge-case of updating start_date of an existing relationship
1509 if (!empty($params['start_date'])) {
1510 $startDate = substr(CRM_Utils_Date::format($params['start_date']), 0, 8);
1511 if ($today < $startDate) {
1512 return;
1513 }
1514 }
1515
1516 if (!empty($params['end_date'])) {
1517 $endDate = substr(CRM_Utils_Date::format($params['end_date']), 0, 8);
1518 if ($today > $endDate) {
1519 $status = self::PAST;
1520 }
1521 }
1522
1523 if (($action & CRM_Core_Action::ADD) && ($status & self::PAST)) {
1524 // If relationship is PAST and action is ADD, do nothing.
1525 return;
1526 }
1527
1528 $rel = explode('_', $params['relationship_type_id']);
1529
1530 $relTypeId = $rel[0];
1531 if (!empty($rel[1])) {
1532 $relDirection = "_{$rel[1]}_{$rel[2]}";
1533 }
1534 else {
1535 // this call is coming from somewhere where the direction was resolved early on (e.g an api call)
1536 // so we can assume _a_b
1537 $relDirection = "_a_b";
1538 $targetContact = array($params['contact_id_b'] => 1);
1539 }
1540
1541 if (($action & CRM_Core_Action::ADD) ||
1542 ($action & CRM_Core_Action::DELETE)
1543 ) {
1544 $contact = $contactId;
1545 }
1546 elseif ($action & CRM_Core_Action::UPDATE) {
1547 $contact = $ids['contact'];
1548 $targetContact = array($ids['contactTarget'] => 1);
1549 }
1550
1551 // Build the 'values' array for
1552 // 1. ContactA
1553 // 2. ContactB
1554 // This will allow us to check if either of the contacts in
1555 // relationship have active memberships.
1556
1557 $values = array();
1558
1559 // 1. ContactA
1560 $values[$contact] = array(
1561 'relatedContacts' => $targetContact,
1562 'relationshipTypeId' => $relTypeId,
1563 'relationshipTypeDirection' => $relDirection,
1564 );
1565 // 2. ContactB
1566 if (!empty($targetContact)) {
1567 foreach ($targetContact as $cid => $donCare) {
1568 $values[$cid] = array(
1569 'relatedContacts' => array($contact => 1),
1570 'relationshipTypeId' => $relTypeId,
1571 );
1572
1573 $relTypeParams = array('id' => $relTypeId);
1574 $relTypeValues = array();
1575 CRM_Contact_BAO_RelationshipType::retrieve($relTypeParams, $relTypeValues);
1576
1577 if (CRM_Utils_Array::value('name_a_b', $relTypeValues) == CRM_Utils_Array::value('name_b_a', $relTypeValues)) {
1578 $values[$cid]['relationshipTypeDirection'] = '_a_b';
1579 }
1580 else {
1581 $values[$cid]['relationshipTypeDirection'] = ($relDirection == '_a_b') ? '_b_a' : '_a_b';
1582 }
1583 }
1584 }
1585
1586 // CRM-15829 UPDATES
1587 // If we're looking for active memberships we must consider pending (id: 5) ones too.
1588 // Hence we can't just call CRM_Member_BAO_Membership::getValues below with the active flag, is it would completely miss pending relatioships.
1589 // 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.
1590 $pendingStatusId = array_search('Pending', CRM_Member_PseudoConstant::membershipStatus());
1591
1592 $query = 'SELECT * FROM `civicrm_membership_status`';
1593 if ($active) {
1594 $query .= ' WHERE `is_current_member` = 1 OR `id` = %1 ';
1595 }
1596
1597 $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($pendingStatusId, 'Integer')));
1598
1599 while ($dao->fetch()) {
1600 $membershipStatusRecordIds[$dao->id] = $dao->id;
1601 }
1602
1603 // Now get the active memberships for all the contacts.
1604 // If contact have any valid membership(s), then add it to
1605 // 'values' array.
1606 foreach ($values as $cid => $subValues) {
1607 $memParams = array('contact_id' => $cid);
1608 $memberships = array();
1609
1610 // CRM-15829 UPDATES
1611 // 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.
1612 CRM_Member_BAO_Membership::getValues($memParams, $memberships, FALSE, TRUE);
1613
1614 // CRM-15829 UPDATES
1615 // filter out the memberships returned by CRM_Member_BAO_Membership::getValues based on the status IDs fetched on line ~1462
1616 foreach ($memberships as $key => $membership) {
1617
1618 if (!isset($memberships[$key]['status_id'])) {
1619 continue;
1620 }
1621
1622 $membershipStatusId = $memberships[$key]['status_id'];
1623 if (!isset($membershipStatusRecordIds[$membershipStatusId])) {
1624 unset($memberships[$key]);
1625 }
1626 }
1627
1628 if (empty($memberships)) {
1629 continue;
1630 }
1631
1632 //get ownerMembershipIds for related Membership
1633 //this is to handle memberships being deleted and recreated
1634 if (!empty($memberships['owner_membership_ids'])) {
1635 $ownerMemIds[$cid] = $memberships['owner_membership_ids'];
1636 unset($memberships['owner_membership_ids']);
1637 }
1638
1639 $values[$cid]['memberships'] = $memberships;
1640 }
1641 $deceasedStatusId = array_search('Deceased', CRM_Member_PseudoConstant::membershipStatus());
1642
1643 // done with 'values' array.
1644 // Finally add / edit / delete memberships for the related contacts
1645
1646 foreach ($values as $cid => $details) {
1647 if (!array_key_exists('memberships', $details)) {
1648 continue;
1649 }
1650
1651 $relatedContacts = array_keys(CRM_Utils_Array::value('relatedContacts', $details, array()));
1652 $mainRelatedContactId = reset($relatedContacts);
1653
1654 foreach ($details['memberships'] as $membershipId => $membershipValues) {
1655 $relTypeIds = array();
1656 if ($action & CRM_Core_Action::DELETE) {
1657 // Delete memberships of the related contacts only if relationship type exists for membership type
1658 $query = "
1659 SELECT relationship_type_id, relationship_direction
1660 FROM civicrm_membership_type
1661 WHERE id = {$membershipValues['membership_type_id']}";
1662 $dao = CRM_Core_DAO::executeQuery($query);
1663 $relTypeDirs = array();
1664 while ($dao->fetch()) {
1665 $relTypeId = $dao->relationship_type_id;
1666 $relDirection = $dao->relationship_direction;
1667 }
1668 $relTypeIds = explode(CRM_Core_DAO::VALUE_SEPARATOR, $relTypeId);
1669 if (in_array($values[$cid]['relationshipTypeId'], $relTypeIds
1670 //CRM-16300 check if owner membership exist for related membership
1671 ) && !empty($membershipValues['owner_membership_id']) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) {
1672 CRM_Member_BAO_Membership::deleteRelatedMemberships($membershipValues['owner_membership_id'], $membershipValues['membership_contact_id']);
1673 }
1674 continue;
1675 }
1676 if (($action & CRM_Core_Action::UPDATE) &&
1677 ($status & self::PAST) &&
1678 ($membershipValues['owner_membership_id'])
1679 ) {
1680 // If relationship is PAST and action is UPDATE
1681 // then delete the RELATED membership
1682 CRM_Member_BAO_Membership::deleteRelatedMemberships($membershipValues['owner_membership_id'],
1683 $membershipValues['membership_contact_id']
1684 );
1685 continue;
1686 }
1687
1688 // add / edit the memberships for related
1689 // contacts.
1690
1691 // Get the Membership Type Details.
1692 $membershipType = CRM_Member_BAO_MembershipType::getMembershipTypeDetails($membershipValues['membership_type_id']);
1693 // Check if contact's relationship type exists in membership type
1694 $relTypeDirs = array();
1695 if (!empty($membershipType['relationship_type_id'])) {
1696 $relTypeIds = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_type_id']);
1697 }
1698 if (!empty($membershipType['relationship_direction'])) {
1699 $relDirections = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_direction']);
1700 }
1701 foreach ($relTypeIds as $key => $value) {
1702 $relTypeDirs[] = $value . '_' . $relDirections[$key];
1703 }
1704 $relTypeDir = $details['relationshipTypeId'] . $details['relationshipTypeDirection'];
1705 if (in_array($relTypeDir, $relTypeDirs)) {
1706 // Check if relationship being created/updated is
1707 // similar to that of membership type's
1708 // relationship.
1709
1710 $membershipValues['owner_membership_id'] = $membershipId;
1711 unset($membershipValues['id']);
1712 unset($membershipValues['membership_contact_id']);
1713 unset($membershipValues['contact_id']);
1714 unset($membershipValues['membership_id']);
1715 foreach ($details['relatedContacts'] as $relatedContactId => $donCare) {
1716 $membershipValues['contact_id'] = $relatedContactId;
1717 if ($deceasedStatusId &&
1718 CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $relatedContactId, 'is_deceased')
1719 ) {
1720 $membershipValues['status_id'] = $deceasedStatusId;
1721 $membershipValues['skipStatusCal'] = TRUE;
1722 }
1723 foreach (array(
1724 'join_date',
1725 'start_date',
1726 'end_date',
1727 ) as $dateField) {
1728 if (!empty($membershipValues[$dateField])) {
1729 $membershipValues[$dateField] = CRM_Utils_Date::processDate($membershipValues[$dateField]);
1730 }
1731 }
1732
1733 if ($action & CRM_Core_Action::UPDATE) {
1734 //if updated relationship is already related to contact don't delete existing inherited membership
1735 if (in_array($relTypeId, $relTypeIds
1736 ) && !empty($values[$relatedContactId]['memberships']) && !empty($ownerMemIds
1737 ) && in_array($membershipValues['owner_membership_id'], $ownerMemIds[$relatedContactId])) {
1738 continue;
1739 }
1740
1741 //delete the membership record for related
1742 //contact before creating new membership record.
1743 CRM_Member_BAO_Membership::deleteRelatedMemberships($membershipId, $relatedContactId);
1744 }
1745 //skip status calculation for pay later memberships.
1746 if (!empty($membershipValues['status_id']) && $membershipValues['status_id'] == $pendingStatusId) {
1747 $membershipValues['skipStatusCal'] = TRUE;
1748 }
1749
1750 // check whether we have some related memberships still available
1751 $query = "
1752 SELECT count(*)
1753 FROM civicrm_membership
1754 LEFT JOIN civicrm_membership_status ON (civicrm_membership_status.id = civicrm_membership.status_id)
1755 WHERE membership_type_id = {$membershipValues['membership_type_id']} AND owner_membership_id = {$membershipValues['owner_membership_id']}
1756 AND is_current_member = 1";
1757 $result = CRM_Core_DAO::singleValueQuery($query);
1758 if ($result < CRM_Utils_Array::value('max_related', $membershipValues, PHP_INT_MAX)) {
1759 CRM_Member_BAO_Membership::create($membershipValues, CRM_Core_DAO::$_nullArray);
1760 }
1761 }
1762 }
1763 elseif ($action & CRM_Core_Action::UPDATE) {
1764 // if action is update and updated relationship do
1765 // not match with the existing
1766 // membership=>relationship then we need to
1767 // change the status of the membership record to expired for
1768 // previous relationship -- CRM-12078.
1769 // CRM-16087 we need to pass ownerMembershipId to isRelatedMembershipExpired function
1770 if (empty($params['relationship_ids']) && !empty($params['id'])) {
1771 $relIds = array($params['id']);
1772 }
1773 else {
1774 $relIds = CRM_Utils_Array::value('relationship_ids', $params);
1775 }
1776 if (self::isRelatedMembershipExpired($relTypeIds, $contactId, $mainRelatedContactId, $relTypeId,
1777 $relIds) && !empty($membershipValues['owner_membership_id']
1778 ) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) {
1779 $membershipValues['status_id'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus', 'Expired', 'id', 'label');
1780 $type = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $membershipValues['membership_type_id'], 'name', 'id');
1781 CRM_Member_BAO_Membership::add($membershipValues);
1782 CRM_Core_Session::setStatus(ts("Inherited membership {$type} status was changed to Expired due to the change in relationship type."), ts('Record Updated'), 'alert');
1783 }
1784 }
1785 }
1786 }
1787 }
1788
1789 /**
1790 * Helper function to check whether the membership is expired or not.
1791 *
1792 * Function takes a list of related membership types and if it is not also passed a
1793 * relationship ID of that types evaluates whether the membership status should be changed to expired.
1794 *
1795 * @param array $membershipTypeRelationshipTypeIDs
1796 * Relation type IDs related to the given membership type.
1797 * @param int $contactId
1798 * @param int $mainRelatedContactId
1799 * @param int $relTypeId
1800 * @param array $relIds
1801 *
1802 * @return bool
1803 */
1804 public static function isRelatedMembershipExpired($membershipTypeRelationshipTypeIDs, $contactId, $mainRelatedContactId, $relTypeId, $relIds) {
1805 if (empty($membershipTypeRelationshipTypeIDs) || in_array($relTypeId, $membershipTypeRelationshipTypeIDs)) {
1806 return FALSE;
1807 }
1808
1809 if (empty($relIds)) {
1810 return FALSE;
1811 }
1812
1813 $relParamas = array(
1814 1 => array($contactId, 'Integer'),
1815 2 => array($mainRelatedContactId, 'Integer'),
1816 );
1817
1818 if ($contactId == $mainRelatedContactId) {
1819 $recordsFound = (int) CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM civicrm_relationship WHERE relationship_type_id IN ( " . implode(',', $membershipTypeRelationshipTypeIDs) . " ) AND
1820 contact_id_a IN ( %1 ) OR contact_id_b IN ( %1 ) AND id IN (" . implode(',', $relIds) . ")", $relParamas);
1821 if ($recordsFound) {
1822 return FALSE;
1823 }
1824 return TRUE;
1825 }
1826
1827 $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);
1828
1829 if ($recordsFound) {
1830 return FALSE;
1831 }
1832
1833 return TRUE;
1834 }
1835
1836 /**
1837 * Get Current Employer for Contact.
1838 *
1839 * @param $contactIds
1840 * Contact Ids.
1841 *
1842 * @return array
1843 * array of the current employer
1844 */
1845 public static function getCurrentEmployer($contactIds) {
1846 $contacts = implode(',', $contactIds);
1847
1848 $query = "
1849 SELECT organization_name, id, employer_id
1850 FROM civicrm_contact
1851 WHERE id IN ( {$contacts} )
1852 ";
1853
1854 $dao = CRM_Core_DAO::executeQuery($query);
1855 $currentEmployer = array();
1856 while ($dao->fetch()) {
1857 $currentEmployer[$dao->id]['org_id'] = $dao->employer_id;
1858 $currentEmployer[$dao->id]['org_name'] = $dao->organization_name;
1859 }
1860
1861 return $currentEmployer;
1862 }
1863
1864 /**
1865 * Function to return list of permissioned contacts for a given contact and relationship type.
1866 *
1867 * @param int $contactID
1868 * contact id whose permissioned contacts are to be found.
1869 * @param int $relTypeId
1870 * one or more relationship type id's.
1871 * @param string $name
1872 * @param string $contactType
1873 *
1874 * @return array
1875 * Array of contacts
1876 */
1877 public static function getPermissionedContacts($contactID, $relTypeId = NULL, $name = NULL, $contactType = NULL) {
1878 $contacts = array();
1879 $args = array(1 => array($contactID, 'Integer'));
1880 $relationshipTypeClause = $contactTypeClause = '';
1881
1882 if ($relTypeId) {
1883 // @todo relTypeId is only ever passed in as an int. Change this to reflect that -
1884 // probably being overly conservative by not doing so but working on stable release.
1885 $relationshipTypeClause = 'AND cr.relationship_type_id IN (%2) ';
1886 $args[2] = array($relTypeId, 'String');
1887 }
1888
1889 if ($contactType) {
1890 $contactTypeClause = ' AND cr.relationship_type_id = crt.id AND crt.contact_type_b = %3 ';
1891 $args[3] = array($contactType, 'String');
1892 }
1893
1894 $query = "
1895 SELECT cc.id as id, cc.sort_name as name
1896 FROM civicrm_relationship cr, civicrm_contact cc, civicrm_relationship_type crt
1897 WHERE
1898 cr.contact_id_a = %1 AND
1899 cr.is_permission_a_b = 1 AND
1900 IF(cr.end_date IS NULL, 1, (DATEDIFF( CURDATE( ), cr.end_date ) <= 0)) AND
1901 cr.is_active = 1 AND
1902 cc.id = cr.contact_id_b AND
1903 cc.is_deleted = 0
1904 $relationshipTypeClause
1905 $contactTypeClause
1906 ";
1907
1908 if (!empty($name)) {
1909 $name = CRM_Utils_Type::escape($name, 'String');
1910 $query .= "
1911 AND cc.sort_name LIKE '%$name%'";
1912 }
1913
1914 $dao = CRM_Core_DAO::executeQuery($query, $args);
1915 while ($dao->fetch()) {
1916 $contacts[$dao->id] = array(
1917 'name' => $dao->name,
1918 'value' => $dao->id,
1919 );
1920 }
1921
1922 return $contacts;
1923 }
1924
1925 /**
1926 * Merge relationships from otherContact to mainContact.
1927 *
1928 * Called during contact merge operation
1929 *
1930 * @param int $mainId
1931 * Contact id of main contact record.
1932 * @param int $otherId
1933 * Contact id of record which is going to merge.
1934 * @param array $sqls
1935 * (reference) array of sql statements to append to.
1936 *
1937 * @see CRM_Dedupe_Merger::cpTables()
1938 */
1939 public static function mergeRelationships($mainId, $otherId, &$sqls) {
1940 // Delete circular relationships
1941 $sqls[] = "DELETE FROM civicrm_relationship
1942 WHERE (contact_id_a = $mainId AND contact_id_b = $otherId)
1943 OR (contact_id_b = $mainId AND contact_id_a = $otherId)";
1944
1945 // Delete relationship from other contact if main contact already has that relationship
1946 $sqls[] = "DELETE r2
1947 FROM civicrm_relationship r1, civicrm_relationship r2
1948 WHERE r1.relationship_type_id = r2.relationship_type_id
1949 AND r1.id <> r2.id
1950 AND (
1951 r1.contact_id_a = $mainId AND r2.contact_id_a = $otherId AND r1.contact_id_b = r2.contact_id_b
1952 OR r1.contact_id_b = $mainId AND r2.contact_id_b = $otherId AND r1.contact_id_a = r2.contact_id_a
1953 OR (
1954 (r1.contact_id_a = $mainId AND r2.contact_id_b = $otherId AND r1.contact_id_b = r2.contact_id_a
1955 OR r1.contact_id_b = $mainId AND r2.contact_id_a = $otherId AND r1.contact_id_a = r2.contact_id_b)
1956 AND r1.relationship_type_id IN (SELECT id FROM civicrm_relationship_type WHERE name_b_a = name_a_b)
1957 )
1958 )";
1959
1960 // Move relationships
1961 $sqls[] = "UPDATE IGNORE civicrm_relationship SET contact_id_a = $mainId WHERE contact_id_a = $otherId";
1962 $sqls[] = "UPDATE IGNORE civicrm_relationship SET contact_id_b = $mainId WHERE contact_id_b = $otherId";
1963
1964 // Move current employer id (name will get updated later)
1965 $sqls[] = "UPDATE civicrm_contact SET employer_id = $mainId WHERE employer_id = $otherId";
1966 }
1967
1968 /**
1969 * Set 'is_valid' field to false for all relationships whose end date is in the past, ie. are expired.
1970 *
1971 * @return bool
1972 * True on success, false if error is encountered.
1973 */
1974 public static function disableExpiredRelationships() {
1975 $query = "SELECT id FROM civicrm_relationship WHERE is_active = 1 AND end_date < CURDATE()";
1976
1977 $dao = CRM_Core_DAO::executeQuery($query);
1978 while ($dao->fetch()) {
1979 $result = CRM_Contact_BAO_Relationship::setIsActive($dao->id, FALSE);
1980 // Result will be NULL if error occurred. We abort early if error detected.
1981 if ($result == NULL) {
1982 return FALSE;
1983 }
1984 }
1985 return TRUE;
1986 }
1987
1988 /**
1989 * Function filters the query by possible relationships for the membership type.
1990 *
1991 * It is intended to be called when constructing queries for the api (reciprocal & non-reciprocal)
1992 * and to add clauses to limit the return to those relationships which COULD inherit a membership type
1993 * (as opposed to those who inherit a particular membership
1994 *
1995 * @param array $params
1996 * Api input array.
1997 * @param null $direction
1998 *
1999 * @return array|void
2000 */
2001 public static function membershipTypeToRelationshipTypes(&$params, $direction = NULL) {
2002 $membershipType = civicrm_api3('membership_type', 'getsingle', array(
2003 'id' => $params['membership_type_id'],
2004 'return' => 'relationship_type_id, relationship_direction',
2005 ));
2006 $relationshipTypes = $membershipType['relationship_type_id'];
2007 if (empty($relationshipTypes)) {
2008 return NULL;
2009 }
2010 // if we don't have any contact data we can only filter on type
2011 if (empty($params['contact_id']) && empty($params['contact_id_a']) && empty($params['contact_id_a'])) {
2012 $params['relationship_type_id'] = array('IN' => $relationshipTypes);
2013 return NULL;
2014 }
2015 else {
2016 $relationshipDirections = (array) $membershipType['relationship_direction'];
2017 // if we have contact_id_a OR contact_id_b we can make a call here
2018 // if we have contact??
2019 foreach ($relationshipDirections as $index => $mtdirection) {
2020 if (isset($params['contact_id_a']) && $mtdirection == 'a_b' || $direction == 'a_b') {
2021 $types[] = $relationshipTypes[$index];
2022 }
2023 if (isset($params['contact_id_b']) && $mtdirection == 'b_a' || $direction == 'b_a') {
2024 $types[] = $relationshipTypes[$index];
2025 }
2026 }
2027 if (!empty($types)) {
2028 $params['relationship_type_id'] = array('IN' => $types);
2029 }
2030 elseif (!empty($clauses)) {
2031 return explode(' OR ', $clauses);
2032 }
2033 else {
2034 // effectively setting it to return no results
2035 $params['relationship_type_id'] = 0;
2036 }
2037 }
2038 }
2039
2040 /**
2041 * Wrapper for contact relationship selector.
2042 *
2043 * @param array $params
2044 * Associated array for params record id.
2045 *
2046 * @return array
2047 * associated array of contact relationships
2048 */
2049 public static function getContactRelationshipSelector(&$params) {
2050 // format the params
2051 $params['offset'] = ($params['page'] - 1) * $params['rp'];
2052 $params['sort'] = CRM_Utils_Array::value('sortBy', $params);
2053
2054 if ($params['context'] == 'past') {
2055 $relationshipStatus = CRM_Contact_BAO_Relationship::INACTIVE;
2056 }
2057 elseif ($params['context'] == 'all') {
2058 $relationshipStatus = CRM_Contact_BAO_Relationship::ALL;
2059 }
2060 else {
2061 $relationshipStatus = CRM_Contact_BAO_Relationship::CURRENT;
2062 }
2063
2064 // check logged in user for permission
2065 $page = new CRM_Core_Page();
2066 CRM_Contact_Page_View::checkUserPermission($page, $params['contact_id']);
2067 $permissions = array($page->_permission);
2068 if ($page->_permission == CRM_Core_Permission::EDIT) {
2069 $permissions[] = CRM_Core_Permission::DELETE;
2070 }
2071 $mask = CRM_Core_Action::mask($permissions);
2072
2073 $permissionedContacts = TRUE;
2074 if ($params['context'] != 'user') {
2075 $links = CRM_Contact_Page_View_Relationship::links();
2076 }
2077 else {
2078 $links = CRM_Contact_Page_View_UserDashBoard::links();
2079 $mask = NULL;
2080 }
2081 // get contact relationships
2082 $relationships = CRM_Contact_BAO_Relationship::getRelationship($params['contact_id'],
2083 $relationshipStatus,
2084 $params['rp'], 0, 0,
2085 $links, $mask,
2086 $permissionedContacts,
2087 $params, TRUE
2088 );
2089
2090 $contactRelationships = array();
2091 $params['total'] = $relationships['total_relationships'];
2092 unset($relationships['total_relationships']);
2093 if (!empty($relationships)) {
2094
2095 $displayName = CRM_Contact_BAO_Contact::displayName($params['contact_id']);
2096
2097 // format params
2098 foreach ($relationships as $relationshipId => $values) {
2099 $relationship = array();
2100
2101 $relationship['DT_RowId'] = $values['id'];
2102 $relationship['DT_RowClass'] = 'crm-entity';
2103 if ($values['is_active'] == 0) {
2104 $relationship['DT_RowClass'] .= ' disabled';
2105 }
2106
2107 $relationship['DT_RowAttr'] = array();
2108 $relationship['DT_RowAttr']['data-entity'] = 'relationship';
2109 $relationship['DT_RowAttr']['data-id'] = $values['id'];
2110
2111 //Add image icon for related contacts: CRM-14919; CRM-19668
2112 $contactType = (!empty($values['contact_sub_type'])) ? $values['contact_sub_type'] : $values['contact_type'];
2113 $icon = CRM_Contact_BAO_Contact_Utils::getImage($contactType,
2114 FALSE,
2115 $values['cid']
2116 );
2117 $relationship['sort_name'] = $icon . ' ' . CRM_Utils_System::href(
2118 $values['name'],
2119 'civicrm/contact/view',
2120 "reset=1&cid={$values['cid']}");
2121
2122 $relationship['relation'] = CRM_Utils_Array::value('case', $values, '') . CRM_Utils_System::href(
2123 $values['relation'],
2124 'civicrm/contact/view/rel',
2125 "action=view&reset=1&cid={$values['cid']}&id={$values['id']}&rtype={$values['rtype']}");
2126
2127 if (!empty($values['description'])) {
2128 $relationship['relation'] .= "<p class='description'>{$values['description']}</p>";
2129 }
2130
2131 if ($params['context'] == 'current') {
2132 $smarty = CRM_Core_Smarty::singleton();
2133
2134 $contactCombos = [
2135 [
2136 'permContact' => $params['contact_id'],
2137 'permDisplayName' => $displayName,
2138 'otherContact' => $values['cid'],
2139 'otherDisplayName' => $values['display_name'],
2140 'columnKey' => 'sort_name',
2141 ],
2142 [
2143 'permContact' => $values['cid'],
2144 'permDisplayName' => $values['display_name'],
2145 'otherContact' => $params['contact_id'],
2146 'otherDisplayName' => $displayName,
2147 'columnKey' => 'relation',
2148 ],
2149 ];
2150
2151 foreach ($contactCombos as $combo) {
2152 foreach ([CRM_Contact_BAO_Relationship::EDIT, CRM_Contact_BAO_Relationship::VIEW] as $permType) {
2153 $smarty->assign('permType', $permType);
2154 if (($combo['permContact'] == $values['contact_id_a'] and $values['is_permission_a_b'] == $permType)
2155 || ($combo['permContact'] == $values['contact_id_b'] and $values['is_permission_b_a'] == $permType)
2156 ) {
2157 $smarty->assign('permDisplayName', $combo['permDisplayName']);
2158 $smarty->assign('otherDisplayName', $combo['otherDisplayName']);
2159 $relationship[$combo['columnKey']] .= $smarty->fetch('CRM/Contact/Page/View/RelationshipPerm.tpl');
2160 }
2161 }
2162 }
2163 }
2164
2165 $relationship['start_date'] = CRM_Utils_Date::customFormat($values['start_date']);
2166 $relationship['end_date'] = CRM_Utils_Date::customFormat($values['end_date']);
2167 $relationship['city'] = $values['city'];
2168 $relationship['state'] = $values['state'];
2169 $relationship['email'] = $values['email'];
2170 $relationship['phone'] = $values['phone'];
2171 $relationship['links'] = $values['action'];
2172
2173 array_push($contactRelationships, $relationship);
2174 }
2175 }
2176
2177 $relationshipsDT = array();
2178 $relationshipsDT['data'] = $contactRelationships;
2179 $relationshipsDT['recordsTotal'] = $params['total'];
2180 $relationshipsDT['recordsFiltered'] = $params['total'];
2181
2182 return $relationshipsDT;
2183 }
2184
2185 /**
2186 * @inheritdoc
2187 */
2188 public static function buildOptions($fieldName, $context = NULL, $props = array()) {
2189 if ($fieldName === 'relationship_type_id') {
2190 return self::buildRelationshipTypeOptions($props);
2191 }
2192
2193 return parent::buildOptions($fieldName, $context, $props);
2194 }
2195
2196 /**
2197 * Builds a list of options available for relationship types
2198 *
2199 * @param array $params
2200 * - contact_type: Limits by contact type on the "A" side
2201 * - relationship_id: Used to find the value for contact type for "B" side.
2202 * If contact_a matches provided contact_id then type of contact_b will
2203 * be used. Otherwise uses type of contact_a. Must be used with contact_id
2204 * - contact_id: Limits by contact types of this contact on the "A" side
2205 * - is_form: Returns array with keys indexed for use in a quickform
2206 * - relationship_direction: For relationship types with duplicate names
2207 * on both sides, defines which option should be returned, a_b or b_a
2208 *
2209 * @return array
2210 */
2211 public static function buildRelationshipTypeOptions($params = array()) {
2212 $contactId = CRM_Utils_Array::value('contact_id', $params);
2213 $direction = CRM_Utils_Array::value('relationship_direction', $params, 'a_b');
2214 $relationshipId = CRM_Utils_Array::value('relationship_id', $params);
2215 $contactType = CRM_Utils_Array::value('contact_type', $params);
2216 $isForm = CRM_Utils_Array::value('is_form', $params);
2217 $showAll = FALSE;
2218
2219 // getContactRelationshipType will return an empty set if these are not set
2220 if (!$contactId && !$relationshipId && !$contactType) {
2221 $showAll = TRUE;
2222 }
2223
2224 $labels = self::getContactRelationshipType(
2225 $contactId,
2226 $direction,
2227 $relationshipId,
2228 $contactType,
2229 $showAll,
2230 'label'
2231 );
2232
2233 if ($isForm) {
2234 return $labels;
2235 }
2236
2237 $names = self::getContactRelationshipType(
2238 $contactId,
2239 $direction,
2240 $relationshipId,
2241 $contactType,
2242 $showAll,
2243 'name'
2244 );
2245
2246 // ensure $names contains only entries in $labels
2247 $names = array_intersect_key($names, $labels);
2248
2249 $nameToLabels = array_combine($names, $labels);
2250
2251 return $nameToLabels;
2252 }
2253
2254 /**
2255 * Process the params from api, form and check if current
2256 * employer should be set or unset.
2257 *
2258 * @param array $params
2259 * @param int $relationshipId
2260 * @param int|NULL $updatedRelTypeID
2261 *
2262 * @return bool
2263 * TRUE if current employer needs to be cleared.
2264 */
2265 public static function isCurrentEmployerNeedingToBeCleared($params, $relationshipId, $updatedRelTypeID = NULL) {
2266 $existingTypeID = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Relationship', $relationshipId, 'relationship_type_id');
2267 $existingTypeName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', $existingTypeID, 'name_b_a');
2268 $updatedRelTypeID = $updatedRelTypeID ? $updatedRelTypeID : $existingTypeID;
2269
2270 if ($existingTypeName !== 'Employer of') {
2271 return FALSE;
2272 }
2273 //Clear employer if relationship is expired.
2274 if (!empty($params['end_date']) && strtotime($params['end_date']) < time()) {
2275 return TRUE;
2276 }
2277 //current employer checkbox is disabled on the form.
2278 //inactive or relationship type(employer of) is updated.
2279 if ((isset($params['is_current_employer']) && empty($params['is_current_employer']))
2280 || ((isset($params['is_active']) && empty($params['is_active'])))
2281 || $existingTypeID != $updatedRelTypeID) {
2282 return TRUE;
2283 }
2284
2285 return FALSE;
2286 }
2287
2288 }