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