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