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