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