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