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