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