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