Merge pull request #13966 from agileware/CIVICRM-1168
[civicrm-core.git] / CRM / Contact / Form / Relationship.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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 $this->setEmploymentRelationship($params, $relationshipIds);
420
421 // Refresh contact tabs which might have been affected
422 $this->ajaxResponse = [
423 'reloadBlocks' => ['#crm-contactinfo-content'],
424 'updateTabs' => [
425 '#tab_member' => CRM_Contact_BAO_Contact::getCountComponent('membership', $this->_contactId),
426 '#tab_contribute' => CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId),
427 ],
428 ];
429 }
430
431 /**
432 * Date validation.
433 *
434 * @param array $params
435 * (reference ) an assoc array of name/value pairs.
436 *
437 * @return bool|array
438 * mixed true or array of errors
439 */
440 public static function dateRule($params) {
441 $errors = [];
442
443 // check start and end date
444 if (!empty($params['start_date']) && !empty($params['end_date'])) {
445 if ($params['end_date'] < $params['start_date']) {
446 $errors['end_date'] = ts('The relationship end date cannot be prior to the start date.');
447 }
448 }
449
450 return empty($errors) ? TRUE : $errors;
451 }
452
453 /**
454 * Set Status message to reflect outcome of the update action.
455 *
456 * @param array $outcome
457 * Outcome of save action - including
458 * - 'valid' : Number of valid relationships attempted.
459 * - 'invalid' : Number of invalid relationships attempted.
460 * - 'duplicate' : Number of duplicate relationships attempted.
461 * - 'saved' : boolean of whether save was successful
462 */
463 protected function setMessage($outcome) {
464 if (!empty($outcome['valid']) && empty($outcome['saved'])) {
465 CRM_Core_Session::setStatus(ts('Relationship created.', [
466 'count' => $outcome['valid'],
467 'plural' => '%count relationships created.',
468 ]), ts('Saved'), 'success');
469 }
470 if (!empty($outcome['invalid'])) {
471 CRM_Core_Session::setStatus(ts('%count relationship record was not created due to an invalid contact type.', [
472 'count' => $outcome['invalid'],
473 'plural' => '%count relationship records were not created due to invalid contact types.',
474 ]), ts('%count invalid relationship record', [
475 'count' => $outcome['invalid'],
476 'plural' => '%count invalid relationship records',
477 ]));
478 }
479 if (!empty($outcome['duplicate'])) {
480 CRM_Core_Session::setStatus(ts('One relationship was not created because it already exists.', [
481 'count' => $outcome['duplicate'],
482 'plural' => '%count relationships were not created because they already exist.',
483 ]), ts('%count duplicate relationship', [
484 'count' => $outcome['duplicate'],
485 'plural' => '%count duplicate relationships',
486 ]));
487 }
488 if (!empty($outcome['saved'])) {
489 CRM_Core_Session::setStatus(ts('Relationship record has been updated.'), ts('Saved'), 'success');
490 }
491 }
492
493 /**
494 * @param $relationshipList
495 *
496 * @return array
497 */
498 public static function getRelationshipTypeMetadata($relationshipList) {
499 $contactTypes = CRM_Contact_BAO_ContactType::contactTypeInfo(TRUE);
500 $allRelationshipNames = CRM_Core_PseudoConstant::relationshipType('name');
501 $jsData = [];
502 // Get just what we need to keep the dom small
503 $whatWeWant = array_flip([
504 'contact_type_a',
505 'contact_type_b',
506 'contact_sub_type_a',
507 'contact_sub_type_b',
508 ]);
509 foreach ($allRelationshipNames as $id => $vals) {
510 if (isset($relationshipList["{$id}_a_b"]) || isset($relationshipList["{$id}_b_a"])) {
511 $jsData[$id] = array_filter(array_intersect_key($allRelationshipNames[$id], $whatWeWant));
512 // Add user-friendly placeholder
513 foreach (['a', 'b'] as $x) {
514 $type = !empty($jsData[$id]["contact_sub_type_$x"]) ? $jsData[$id]["contact_sub_type_$x"] : CRM_Utils_Array::value("contact_type_$x", $jsData[$id]);
515 $jsData[$id]["placeholder_$x"] = $type ? ts('- select %1 -', [strtolower($contactTypes[$type]['label'])]) : ts('- select contact -');
516 }
517 }
518 }
519 return $jsData;
520 }
521
522 /**
523 * Handling 'delete relationship' action
524 *
525 * @param int $id
526 * Relationship ID
527 */
528 private function deleteAction($id) {
529 CRM_Contact_BAO_Relationship::del($id);
530
531 // reload all blocks to reflect this change on the user interface.
532 $this->ajaxResponse['reloadBlocks'] = ['#crm-contactinfo-content'];
533 }
534
535 /**
536 * Handling updating relationship action
537 *
538 * @param array $params
539 *
540 * @return array
541 * @throws \CRM_Core_Exception
542 */
543 private function updateAction($params) {
544 list($params, $_) = $this->preparePostProcessParameters($params);
545 try {
546 civicrm_api3('relationship', 'create', $params);
547 }
548 catch (CiviCRM_API3_Exception $e) {
549 throw new CRM_Core_Exception('Relationship create error ' . $e->getMessage());
550 }
551
552 $this->setMessage(['saved' => TRUE]);
553 return [$params, [$this->_relationshipId]];
554 }
555
556 /**
557 * Handling creating relationship action
558 *
559 * @param array $params
560 *
561 * @return array
562 * @throws \CRM_Core_Exception
563 */
564 private function createAction($params) {
565 list($params, $primaryContactLetter) = $this->preparePostProcessParameters($params);
566
567 $outcome = CRM_Contact_BAO_Relationship::createMultiple($params, $primaryContactLetter);
568
569 $relationshipIds = $outcome['relationship_ids'];
570
571 $this->setMessage($outcome);
572
573 return [$params, $relationshipIds];
574 }
575
576 /**
577 * Prepares parameters to be used for create/update actions
578 *
579 * @param array $values
580 *
581 * @return array
582 */
583 private function preparePostProcessParameters($values) {
584 $params = $values;
585 list($relationshipTypeId, $a, $b) = explode('_', $params['relationship_type_id']);
586
587 $params['relationship_type_id'] = $relationshipTypeId;
588 $params['contact_id_' . $a] = $this->_contactId;
589
590 if (empty($this->_relationshipId)) {
591 $params['contact_id_' . $b] = explode(',', $params['related_contact_id']);
592 }
593 else {
594 $params['id'] = $this->_relationshipId;
595 $params['contact_id_' . $b] = $params['related_contact_id'];
596 }
597
598 // If this is a b_a relationship these form elements are flipped
599 $params['is_permission_a_b'] = CRM_Utils_Array::value("is_permission_{$a}_{$b}", $values, 0);
600 $params['is_permission_b_a'] = CRM_Utils_Array::value("is_permission_{$b}_{$a}", $values, 0);
601
602 return [$params, $a];
603 }
604
605 /**
606 * Updates/Creates relationship notes
607 *
608 * @param array $relationshipIds
609 * @param string $note
610 *
611 * @throws \CiviCRM_API3_Exception
612 */
613 private function saveRelationshipNotes($relationshipIds, $note) {
614 foreach ($relationshipIds as $id) {
615 $noteParams = [
616 'entity_id' => $id,
617 'entity_table' => 'civicrm_relationship',
618 ];
619
620 $existing = civicrm_api3('note', 'get', $noteParams);
621 if (!empty($existing['id'])) {
622 $noteParams['id'] = $existing['id'];
623 }
624
625 $action = NULL;
626 if (!empty($note)) {
627 $action = 'create';
628 $noteParams['note'] = $note;
629 $noteParams['contact_id'] = $this->_contactId;
630 }
631 elseif (!empty($noteParams['id'])) {
632 $action = 'delete';
633 }
634
635 if (!empty($action)) {
636 civicrm_api3('note', $action, $noteParams);
637 }
638 }
639 }
640
641 /**
642 * Sets current employee/employer relationship
643 *
644 * @param $params
645 * @param array $relationshipIds
646 */
647 private function setEmploymentRelationship($params, $relationshipIds) {
648 $employerParams = [];
649 foreach ($relationshipIds as $id) {
650 if (!CRM_Contact_BAO_Relationship::isCurrentEmployerNeedingToBeCleared($params, $id)
651 //don't think this is required to check again.
652 && $this->_allRelationshipNames[$params['relationship_type_id']]["name_a_b"] == 'Employee of') {
653 // Fixme this is dumb why do we have to look this up again?
654 $rel = CRM_Contact_BAO_Relationship::getRelationshipByID($id);
655 $employerParams[$rel->contact_id_a] = $rel->contact_id_b;
656 }
657 }
658 if (!empty($employerParams)) {
659 // @todo this belongs in the BAO.
660 CRM_Contact_BAO_Contact_Utils::setCurrentEmployer($employerParams);
661 }
662 }
663
664 }