Merge pull request #15784 from civicrm/5.20
[civicrm-core.git] / CRM / Contact / Form / Relationship.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * This class generates form components for relationship.
30 */
31 class CRM_Contact_Form_Relationship extends CRM_Core_Form {
32
33 /**
34 * The relationship id, used when editing the relationship
35 *
36 * @var int
37 */
38 public $_relationshipId;
39
40 /**
41 * The contact id, used when add/edit relationship
42 *
43 * @var int
44 */
45 public $_contactId;
46
47 /**
48 * This is a string which is either a_b or b_a used to determine the relationship between to contacts
49 * @var string
50 */
51 public $_rtype;
52
53 /**
54 * This is a string which is used to determine the relationship between to contacts
55 * @var string
56 */
57 public $_rtypeId;
58
59 /**
60 * Display name of contact a
61 * @var string
62 */
63 public $_display_name_a;
64
65 /**
66 * Display name of contact b
67 * @var string
68 */
69 public $_display_name_b;
70
71 /**
72 * The relationship type id
73 *
74 * @var int
75 */
76 public $_relationshipTypeId;
77
78 /**
79 * An array of all relationship names
80 *
81 * @var array
82 */
83 public $_allRelationshipNames;
84
85 /**
86 * @var bool
87 */
88 public $_enabled;
89
90 /**
91 * @var bool
92 */
93 public $_isCurrentEmployer;
94
95 /**
96 * @var string
97 */
98 public $_contactType;
99
100 /**
101 * The relationship values if Updating relationship
102 * @var array
103 */
104 public $_values;
105
106 /**
107 * Case id if it called from case context
108 * @var int
109 */
110 public $_caseId;
111
112 /**
113 * Explicitly declare the form context.
114 */
115 public function getDefaultContext() {
116 return 'create';
117 }
118
119 /**
120 * Explicitly declare the entity api name.
121 */
122 public function getDefaultEntity() {
123 return 'Relationship';
124 }
125
126 public function preProcess() {
127 $this->_contactId = $this->get('contactId');
128
129 $this->_contactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'contact_type');
130
131 $this->_relationshipId = $this->get('id');
132
133 $this->_rtype = CRM_Utils_Request::retrieve('rtype', 'String', $this);
134
135 $this->_rtypeId = CRM_Utils_Request::retrieve('relTypeId', 'String', $this);
136
137 $this->_display_name_a = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'display_name');
138
139 $this->assign('display_name_a', $this->_display_name_a);
140 //get the relationship values.
141 $this->_values = [];
142 if ($this->_relationshipId) {
143 $params = ['id' => $this->_relationshipId];
144 CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_Relationship', $params, $this->_values);
145 }
146
147 // Check for permissions
148 if (in_array($this->_action, [CRM_Core_Action::ADD, CRM_Core_Action::UPDATE, CRM_Core_Action::DELETE])) {
149 if (!CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::EDIT)
150 && !CRM_Contact_BAO_Contact_Permission::allow($this->_values['contact_id_b'], CRM_Core_Permission::EDIT)) {
151 CRM_Core_Error::statusBounce(ts('You do not have the necessary permission to edit this contact.'));
152 }
153 }
154
155 // Set page title based on action
156 switch ($this->_action) {
157 case CRM_Core_Action::VIEW:
158 CRM_Utils_System::setTitle(ts('View Relationship for %1', [1 => $this->_display_name_a]));
159 break;
160
161 case CRM_Core_Action::ADD:
162 CRM_Utils_System::setTitle(ts('Add Relationship for %1', [1 => $this->_display_name_a]));
163 break;
164
165 case CRM_Core_Action::UPDATE:
166 CRM_Utils_System::setTitle(ts('Edit Relationship for %1', [1 => $this->_display_name_a]));
167 break;
168
169 case CRM_Core_Action::DELETE:
170 CRM_Utils_System::setTitle(ts('Delete Relationship for %1', [1 => $this->_display_name_a]));
171 break;
172 }
173
174 $this->_caseId = CRM_Utils_Request::retrieve('caseID', 'Integer', $this);
175
176 if (!$this->_rtypeId) {
177 $params = CRM_Utils_Request::exportValues();
178 if (isset($params['relationship_type_id'])) {
179 $this->_rtypeId = $params['relationship_type_id'];
180 }
181 elseif (!empty($this->_values)) {
182 $this->_rtypeId = $this->_values['relationship_type_id'] . '_' . $this->_rtype;
183 }
184 }
185
186 //get the relationship type id
187 $this->_relationshipTypeId = str_replace(['_a_b', '_b_a'], ['', ''], $this->_rtypeId);
188
189 //get the relationship type
190 if (!$this->_rtype) {
191 $this->_rtype = str_replace($this->_relationshipTypeId . '_', '', $this->_rtypeId);
192 }
193
194 //need to assign custom data type and subtype to the template - FIXME: explain why
195 $this->assign('customDataType', 'Relationship');
196 $this->assign('customDataSubType', $this->_relationshipTypeId);
197 $this->assign('entityID', $this->_relationshipId);
198
199 //use name as it remain constant, CRM-3336
200 $this->_allRelationshipNames = CRM_Core_PseudoConstant::relationshipType('name');
201
202 // Current employer?
203 if ($this->_action & CRM_Core_Action::UPDATE) {
204 if ($this->_allRelationshipNames[$this->_relationshipTypeId]["name_a_b"] == 'Employee of') {
205 $this->_isCurrentEmployer = $this->_values['contact_id_b'] == CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_values['contact_id_a'], 'employer_id');
206 }
207 }
208
209 // when custom data is included in this page
210 if (!empty($_POST['hidden_custom'])) {
211 CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_relationshipTypeId, 1, 'Relationship', $this->_relationshipId);
212 CRM_Custom_Form_CustomData::buildQuickForm($this);
213 CRM_Custom_Form_CustomData::setDefaultValues($this);
214 }
215 }
216
217 /**
218 * Set default values for the form.
219 */
220 public function setDefaultValues() {
221 $defaults = [];
222
223 if ($this->_action & CRM_Core_Action::UPDATE) {
224 if (!empty($this->_values)) {
225 $defaults['relationship_type_id'] = $this->_rtypeId;
226 $defaults['start_date'] = CRM_Utils_Array::value('start_date', $this->_values);
227 $defaults['end_date'] = CRM_Utils_Array::value('end_date', $this->_values);
228 $defaults['description'] = CRM_Utils_Array::value('description', $this->_values);
229 $defaults['is_active'] = CRM_Utils_Array::value('is_active', $this->_values);
230
231 // The postprocess function will swap these fields if it is a b_a relationship, so we compensate here
232 $defaults['is_permission_a_b'] = CRM_Utils_Array::value('is_permission_' . $this->_rtype, $this->_values);
233 $defaults['is_permission_b_a'] = CRM_Utils_Array::value('is_permission_' . strrev($this->_rtype), $this->_values);
234
235 $defaults['is_current_employer'] = $this->_isCurrentEmployer;
236
237 // Load info about the related contact
238 $contact = new CRM_Contact_DAO_Contact();
239 if ($this->_rtype == 'a_b' && $this->_values['contact_id_a'] == $this->_contactId) {
240 $contact->id = $this->_values['contact_id_b'];
241 }
242 else {
243 $contact->id = $this->_values['contact_id_a'];
244 }
245 if ($contact->find(TRUE)) {
246 $defaults['related_contact_id'] = $contact->id;
247 $this->_display_name_b = $contact->display_name;
248 $this->assign('display_name_b', $this->_display_name_b);
249 }
250
251 $noteParams = [
252 'entity_id' => $this->_relationshipId,
253 'entity_table' => 'civicrm_relationship',
254 'limit' => 1,
255 'version' => 3,
256 ];
257 $note = civicrm_api('Note', 'getsingle', $noteParams);
258 $defaults['note'] = CRM_Utils_Array::value('note', $note);
259 }
260 }
261 else {
262 $defaults['is_active'] = $defaults['is_current_employer'] = 1;
263 $defaults['relationship_type_id'] = $this->_rtypeId;
264 $defaults['is_permission_a_b'] = $defaults['is_permission_b_a'] = CRM_Contact_BAO_Relationship::NONE;
265 }
266
267 $this->_enabled = $defaults['is_active'];
268 return $defaults;
269 }
270
271 /**
272 * Add the rules for form.
273 */
274 public function addRules() {
275 if (!($this->_action & CRM_Core_Action::DELETE)) {
276 $this->addFormRule(['CRM_Contact_Form_Relationship', 'dateRule']);
277 }
278 }
279
280 /**
281 * Build the form object.
282 */
283 public function buildQuickForm() {
284 if ($this->_action & CRM_Core_Action::DELETE) {
285 $this->addButtons([
286 [
287 'type' => 'next',
288 'name' => ts('Delete'),
289 'isDefault' => TRUE,
290 ],
291 [
292 'type' => 'cancel',
293 'name' => ts('Cancel'),
294 ],
295 ]);
296 return;
297 }
298
299 // Select list
300 $relationshipList = CRM_Contact_BAO_Relationship::getContactRelationshipType($this->_contactId, $this->_rtype, $this->_relationshipId);
301
302 $this->assign('contactTypes', CRM_Contact_BAO_ContactType::contactTypeInfo(TRUE));
303
304 foreach ($this->_allRelationshipNames as $id => $vals) {
305 if ($vals['name_a_b'] === 'Employee of') {
306 $this->assign('employmentRelationship', $id);
307 break;
308 }
309 }
310
311 $this->addField(
312 'relationship_type_id',
313 [
314 'options' => ['' => ts('- select -')] + $relationshipList,
315 'class' => 'huge',
316 'placeholder' => '- select -',
317 'option_url' => 'civicrm/admin/reltype',
318 'option_context' => [
319 'contact_id' => $this->_contactId,
320 'relationship_direction' => $this->_rtype,
321 'relationship_id' => $this->_relationshipId,
322 'is_form' => TRUE,
323 ],
324 ],
325 TRUE
326 );
327
328 $label = $this->_action & CRM_Core_Action::ADD ? ts('Contact(s)') : ts('Contact');
329 $contactField = $this->addField('related_contact_id', ['label' => $label, 'name' => 'contact_id_b', 'multiple' => TRUE, 'create' => TRUE], TRUE);
330 // This field cannot be updated
331 if ($this->_action & CRM_Core_Action::UPDATE) {
332 $contactField->freeze();
333 }
334
335 $this->add('advcheckbox', 'is_current_employer', $this->_contactType == 'Organization' ? ts('Current Employee') : ts('Current Employer'));
336
337 $this->addField('start_date', ['label' => ts('Start Date')], FALSE, FALSE);
338 $this->addField('end_date', ['label' => ts('End Date')], FALSE, FALSE);
339
340 $this->addField('is_active', ['label' => ts('Enabled?'), 'type' => 'advcheckbox']);
341
342 $this->addField('is_permission_a_b', [], TRUE);
343 $this->addField('is_permission_b_a', [], TRUE);
344
345 $this->addField('description', ['label' => ts('Description')]);
346
347 CRM_Contact_Form_Edit_Notes::buildQuickForm($this);
348
349 if ($this->_action & CRM_Core_Action::VIEW) {
350 $this->addButtons([
351 [
352 'type' => 'cancel',
353 'name' => ts('Done'),
354 ],
355 ]);
356 }
357 else {
358 // make this form an upload since we don't know if the custom data injected dynamically is of type file etc.
359 $this->addButtons([
360 [
361 'type' => 'upload',
362 'name' => ts('Save Relationship'),
363 'isDefault' => TRUE,
364 ],
365 [
366 'type' => 'cancel',
367 'name' => ts('Cancel'),
368 ],
369 ]);
370 }
371 }
372
373 /**
374 * This function is called when the form is submitted and also from unit test.
375 *
376 * @param array $params
377 *
378 * @return array
379 * @throws \CRM_Core_Exception
380 */
381 public function submit($params) {
382 switch ($this->getAction()) {
383 case CRM_Core_Action::DELETE:
384 $this->deleteAction($this->_relationshipId);
385 return [];
386
387 case CRM_Core_Action::UPDATE:
388 return $this->updateAction($params);
389
390 default:
391 return $this->createAction($params);
392 }
393 }
394
395 /**
396 * This function is called when the form is submitted.
397 */
398 public function postProcess() {
399 // Store the submitted values in an array.
400 $params = $this->controller->exportValues($this->_name);
401
402 $values = $this->submit($params);
403 if (empty($values)) {
404 return;
405 }
406 list ($params, $relationshipIds) = $values;
407
408 // if this is called from case view,
409 //create an activity for case role removal.CRM-4480
410 // @todo this belongs in the BAO.
411 if ($this->_caseId) {
412 CRM_Case_BAO_Case::createCaseRoleActivity($this->_caseId, $relationshipIds, $params['contact_check'], $this->_contactId);
413 }
414
415 // @todo this belongs in the BAO.
416 $note = !empty($params['note']) ? $params['note'] : '';
417 $this->saveRelationshipNotes($relationshipIds, $note);
418
419 // Refresh contact tabs which might have been affected
420 $this->ajaxResponse = [
421 'reloadBlocks' => ['#crm-contactinfo-content'],
422 'updateTabs' => [
423 '#tab_member' => CRM_Contact_BAO_Contact::getCountComponent('membership', $this->_contactId),
424 '#tab_contribute' => CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId),
425 ],
426 ];
427 }
428
429 /**
430 * Date validation.
431 *
432 * @param array $params
433 * (reference ) an assoc array of name/value pairs.
434 *
435 * @return bool|array
436 * mixed true or array of errors
437 */
438 public static function dateRule($params) {
439 $errors = [];
440
441 // check start and end date
442 if (!empty($params['start_date']) && !empty($params['end_date'])) {
443 if ($params['end_date'] < $params['start_date']) {
444 $errors['end_date'] = ts('The relationship end date cannot be prior to the start date.');
445 }
446 }
447
448 return empty($errors) ? TRUE : $errors;
449 }
450
451 /**
452 * Set Status message to reflect outcome of the update action.
453 *
454 * @param array $outcome
455 * Outcome of save action - including
456 * - 'valid' : Number of valid relationships attempted.
457 * - 'invalid' : Number of invalid relationships attempted.
458 * - 'duplicate' : Number of duplicate relationships attempted.
459 * - 'saved' : boolean of whether save was successful
460 */
461 protected function setMessage($outcome) {
462 if (!empty($outcome['valid']) && empty($outcome['saved'])) {
463 CRM_Core_Session::setStatus(ts('Relationship created.', [
464 'count' => $outcome['valid'],
465 'plural' => '%count relationships created.',
466 ]), ts('Saved'), 'success');
467 }
468 if (!empty($outcome['invalid'])) {
469 CRM_Core_Session::setStatus(ts('%count relationship record was not created due to an invalid contact type.', [
470 'count' => $outcome['invalid'],
471 'plural' => '%count relationship records were not created due to invalid contact types.',
472 ]), ts('%count invalid relationship record', [
473 'count' => $outcome['invalid'],
474 'plural' => '%count invalid relationship records',
475 ]));
476 }
477 if (!empty($outcome['duplicate'])) {
478 CRM_Core_Session::setStatus(ts('One relationship was not created because it already exists.', [
479 'count' => $outcome['duplicate'],
480 'plural' => '%count relationships were not created because they already exist.',
481 ]), ts('%count duplicate relationship', [
482 'count' => $outcome['duplicate'],
483 'plural' => '%count duplicate relationships',
484 ]));
485 }
486 if (!empty($outcome['saved'])) {
487 CRM_Core_Session::setStatus(ts('Relationship record has been updated.'), ts('Saved'), 'success');
488 }
489 }
490
491 /**
492 * @param $relationshipList
493 *
494 * @return array
495 */
496 public static function getRelationshipTypeMetadata($relationshipList) {
497 $contactTypes = CRM_Contact_BAO_ContactType::contactTypeInfo(TRUE);
498 $allRelationshipNames = CRM_Core_PseudoConstant::relationshipType('name');
499 $jsData = [];
500 // Get just what we need to keep the dom small
501 $whatWeWant = array_flip([
502 'contact_type_a',
503 'contact_type_b',
504 'contact_sub_type_a',
505 'contact_sub_type_b',
506 ]);
507 foreach ($allRelationshipNames as $id => $vals) {
508 if (isset($relationshipList["{$id}_a_b"]) || isset($relationshipList["{$id}_b_a"])) {
509 $jsData[$id] = array_filter(array_intersect_key($allRelationshipNames[$id], $whatWeWant));
510 // Add user-friendly placeholder
511 foreach (['a', 'b'] as $x) {
512 $type = !empty($jsData[$id]["contact_sub_type_$x"]) ? $jsData[$id]["contact_sub_type_$x"] : CRM_Utils_Array::value("contact_type_$x", $jsData[$id]);
513 $jsData[$id]["placeholder_$x"] = $type ? ts('- select %1 -', [strtolower($contactTypes[$type]['label'])]) : ts('- select contact -');
514 }
515 }
516 }
517 return $jsData;
518 }
519
520 /**
521 * Handling 'delete relationship' action
522 *
523 * @param int $id
524 * Relationship ID
525 */
526 private function deleteAction($id) {
527 CRM_Contact_BAO_Relationship::del($id);
528
529 // reload all blocks to reflect this change on the user interface.
530 $this->ajaxResponse['reloadBlocks'] = ['#crm-contactinfo-content'];
531 }
532
533 /**
534 * Handling updating relationship action
535 *
536 * @param array $params
537 *
538 * @return array
539 * @throws \CRM_Core_Exception
540 */
541 private function updateAction($params) {
542 list($params, $_) = $this->preparePostProcessParameters($params);
543 try {
544 civicrm_api3('relationship', 'create', $params);
545 }
546 catch (CiviCRM_API3_Exception $e) {
547 throw new CRM_Core_Exception('Relationship create error ' . $e->getMessage());
548 }
549
550 $this->setMessage(['saved' => TRUE]);
551 return [$params, [$this->_relationshipId]];
552 }
553
554 /**
555 * Handling creating relationship action
556 *
557 * @param array $params
558 *
559 * @return array
560 * @throws \CRM_Core_Exception
561 */
562 private function createAction($params) {
563 list($params, $primaryContactLetter) = $this->preparePostProcessParameters($params);
564
565 $outcome = CRM_Contact_BAO_Relationship::createMultiple($params, $primaryContactLetter);
566
567 $relationshipIds = $outcome['relationship_ids'];
568
569 $this->setMessage($outcome);
570
571 return [$params, $relationshipIds];
572 }
573
574 /**
575 * Prepares parameters to be used for create/update actions
576 *
577 * @param array $values
578 *
579 * @return array
580 */
581 private function preparePostProcessParameters($values) {
582 $params = $values;
583 list($relationshipTypeId, $a, $b) = explode('_', $params['relationship_type_id']);
584
585 $params['relationship_type_id'] = $relationshipTypeId;
586 $params['contact_id_' . $a] = $this->_contactId;
587
588 if (empty($this->_relationshipId)) {
589 $params['contact_id_' . $b] = explode(',', $params['related_contact_id']);
590 }
591 else {
592 $params['id'] = $this->_relationshipId;
593 $params['contact_id_' . $b] = $params['related_contact_id'];
594 }
595
596 // If this is a b_a relationship these form elements are flipped
597 $params['is_permission_a_b'] = CRM_Utils_Array::value("is_permission_{$a}_{$b}", $values, 0);
598 $params['is_permission_b_a'] = CRM_Utils_Array::value("is_permission_{$b}_{$a}", $values, 0);
599
600 return [$params, $a];
601 }
602
603 /**
604 * Updates/Creates relationship notes
605 *
606 * @param array $relationshipIds
607 * @param string $note
608 *
609 * @throws \CiviCRM_API3_Exception
610 */
611 private function saveRelationshipNotes($relationshipIds, $note) {
612 foreach ($relationshipIds as $id) {
613 $noteParams = [
614 'entity_id' => $id,
615 'entity_table' => 'civicrm_relationship',
616 ];
617
618 $existing = civicrm_api3('note', 'get', $noteParams);
619 if (!empty($existing['id'])) {
620 $noteParams['id'] = $existing['id'];
621 }
622
623 $action = NULL;
624 if (!empty($note)) {
625 $action = 'create';
626 $noteParams['note'] = $note;
627 $noteParams['contact_id'] = $this->_contactId;
628 }
629 elseif (!empty($noteParams['id'])) {
630 $action = 'delete';
631 }
632
633 if (!empty($action)) {
634 civicrm_api3('note', $action, $noteParams);
635 }
636 }
637 }
638
639 }