3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
13 * This class generates form components for relationship.
15 class CRM_Contact_Form_Relationship
extends CRM_Core_Form
{
18 * The relationship id, used when editing the relationship
22 public $_relationshipId;
25 * The contact id, used when add/edit relationship
32 * This is a string which is either a_b or b_a used to determine the relationship between to contacts
38 * This is a string which is used to determine the relationship between to contacts
44 * Display name of contact a
47 public $_display_name_a;
50 * Display name of contact b
53 public $_display_name_b;
56 * The relationship type id
60 public $_relationshipTypeId;
63 * An array of all relationship names
67 public $_allRelationshipNames;
77 public $_isCurrentEmployer;
85 * The relationship values if Updating relationship
91 * Case id if it called from case context
97 * Explicitly declare the form context.
99 public function getDefaultContext() {
104 * Explicitly declare the entity api name.
106 public function getDefaultEntity() {
107 return 'Relationship';
110 public function preProcess() {
111 $this->_contactId
= $this->get('contactId');
113 $this->_contactType
= CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId
, 'contact_type');
115 $this->_relationshipId
= $this->get('id');
117 $this->_rtype
= CRM_Utils_Request
::retrieve('rtype', 'String', $this);
119 $this->_rtypeId
= CRM_Utils_Request
::retrieve('relTypeId', 'String', $this);
121 $this->_display_name_a
= CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId
, 'display_name');
123 $this->assign('display_name_a', $this->_display_name_a
);
124 //get the relationship values.
126 if ($this->_relationshipId
) {
127 $params = ['id' => $this->_relationshipId
];
128 CRM_Core_DAO
::commonRetrieve('CRM_Contact_DAO_Relationship', $params, $this->_values
);
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.'));
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
]));
145 case CRM_Core_Action
::ADD
:
146 CRM_Utils_System
::setTitle(ts('Add Relationship for %1', [1 => $this->_display_name_a
]));
149 case CRM_Core_Action
::UPDATE
:
150 CRM_Utils_System
::setTitle(ts('Edit Relationship for %1', [1 => $this->_display_name_a
]));
153 case CRM_Core_Action
::DELETE
:
154 CRM_Utils_System
::setTitle(ts('Delete Relationship for %1', [1 => $this->_display_name_a
]));
158 $this->_caseId
= CRM_Utils_Request
::retrieve('caseID', 'Integer', $this);
160 if (!$this->_rtypeId
) {
161 $params = CRM_Utils_Request
::exportValues();
162 if (isset($params['relationship_type_id'])) {
163 $this->_rtypeId
= $params['relationship_type_id'];
165 elseif (!empty($this->_values
)) {
166 $this->_rtypeId
= $this->_values
['relationship_type_id'] . '_' . $this->_rtype
;
170 //get the relationship type id
171 $this->_relationshipTypeId
= str_replace(['_a_b', '_b_a'], ['', ''], $this->_rtypeId
);
173 //get the relationship type
174 if (!$this->_rtype
) {
175 $this->_rtype
= str_replace($this->_relationshipTypeId
. '_', '', $this->_rtypeId
);
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
);
183 //use name as it remain constant, CRM-3336
184 $this->_allRelationshipNames
= CRM_Core_PseudoConstant
::relationshipType('name');
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');
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);
202 * Set default values for the form.
204 public function setDefaultValues() {
207 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
208 if (!empty($this->_values
)) {
209 $defaults['relationship_type_id'] = $this->_rtypeId
;
210 $defaults['start_date'] = $this->_values
['start_date'] ??
NULL;
211 $defaults['end_date'] = $this->_values
['end_date'] ??
NULL;
212 $defaults['description'] = $this->_values
['description'] ??
NULL;
213 $defaults['is_active'] = $this->_values
['is_active'] ??
NULL;
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'] = $this->_values
['is_permission_' . $this->_rtype
] ??
NULL;
217 $defaults['is_permission_b_a'] = $this->_values
['is_permission_' . strrev($this->_rtype
)] ??
NULL;
219 $defaults['is_current_employer'] = $this->_isCurrentEmployer
;
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'];
227 $contact->id
= $this->_values
['contact_id_a'];
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
);
236 'entity_id' => $this->_relationshipId
,
237 'entity_table' => 'civicrm_relationship',
241 $note = civicrm_api('Note', 'getsingle', $noteParams);
242 $defaults['note'] = $note['note'] ??
NULL;
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
;
251 $this->_enabled
= $defaults['is_active'];
256 * Add the rules for form.
258 public function addRules() {
259 if (!($this->_action
& CRM_Core_Action
::DELETE
)) {
260 $this->addFormRule(['CRM_Contact_Form_Relationship', 'dateRule']);
265 * Build the form object.
267 public function buildQuickForm() {
268 if ($this->_action
& CRM_Core_Action
::DELETE
) {
272 'name' => ts('Delete'),
277 'name' => ts('Cancel'),
284 $relationshipList = CRM_Contact_BAO_Relationship
::getContactRelationshipType($this->_contactId
, $this->_rtype
, $this->_relationshipId
);
286 $this->assign('contactTypes', CRM_Contact_BAO_ContactType
::contactTypeInfo(TRUE));
288 foreach ($this->_allRelationshipNames
as $id => $vals) {
289 if ($vals['name_a_b'] === 'Employee of') {
290 $this->assign('employmentRelationship', $id);
296 'relationship_type_id',
298 'options' => ['' => ts('- select -')] +
$relationshipList,
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
,
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();
319 $this->add('advcheckbox', 'is_current_employer', $this->_contactType
== 'Organization' ?
ts('Current Employee') : ts('Current Employer'));
321 $this->addField('start_date', ['label' => ts('Start Date')], FALSE, FALSE);
322 $this->addField('end_date', ['label' => ts('End Date')], FALSE, FALSE);
324 $this->addField('is_active', ['label' => ts('Enabled?'), 'type' => 'advcheckbox']);
326 $this->addField('is_permission_a_b', [], TRUE);
327 $this->addField('is_permission_b_a', [], TRUE);
329 $this->addField('description', ['label' => ts('Description')]);
331 CRM_Contact_Form_Edit_Notes
::buildQuickForm($this);
333 if ($this->_action
& CRM_Core_Action
::VIEW
) {
337 'name' => ts('Done'),
342 // make this form an upload since we don't know if the custom data injected dynamically is of type file etc.
346 'name' => ts('Save Relationship'),
351 'name' => ts('Cancel'),
358 * This function is called when the form is submitted and also from unit test.
360 * @param array $params
363 * @throws \CRM_Core_Exception
365 public function submit($params) {
366 switch ($this->getAction()) {
367 case CRM_Core_Action
::DELETE
:
368 $this->deleteAction($this->_relationshipId
);
371 case CRM_Core_Action
::UPDATE
:
372 return $this->updateAction($params);
375 return $this->createAction($params);
380 * This function is called when the form is submitted.
382 public function postProcess() {
383 // Store the submitted values in an array.
384 $params = $this->controller
->exportValues($this->_name
);
386 $values = $this->submit($params);
387 if (empty($values)) {
390 list ($params, $relationshipIds) = $values;
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
);
399 // @todo this belongs in the BAO.
400 $note = !empty($params['note']) ?
$params['note'] : '';
401 $this->saveRelationshipNotes($relationshipIds, $note);
403 // Refresh contact tabs which might have been affected
404 $this->ajaxResponse
= [
405 'reloadBlocks' => ['#crm-contactinfo-content'],
407 '#tab_member' => CRM_Contact_BAO_Contact
::getCountComponent('membership', $this->_contactId
),
408 '#tab_contribute' => CRM_Contact_BAO_Contact
::getCountComponent('contribution', $this->_contactId
),
416 * @param array $params
417 * (reference ) an assoc array of name/value pairs.
420 * mixed true or array of errors
422 public static function dateRule($params) {
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.');
432 return empty($errors) ?
TRUE : $errors;
436 * Set Status message to reflect outcome of the update action.
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
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');
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',
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',
470 if (!empty($outcome['saved'])) {
471 CRM_Core_Session
::setStatus(ts('Relationship record has been updated.'), ts('Saved'), 'success');
476 * @param $relationshipList
480 public static function getRelationshipTypeMetadata($relationshipList) {
481 $contactTypes = CRM_Contact_BAO_ContactType
::contactTypeInfo(TRUE);
482 $allRelationshipNames = CRM_Core_PseudoConstant
::relationshipType('name');
484 // Get just what we need to keep the dom small
485 $whatWeWant = array_flip([
488 'contact_sub_type_a',
489 'contact_sub_type_b',
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 -');
505 * Handling 'delete relationship' action
510 private function deleteAction($id) {
511 CRM_Contact_BAO_Relationship
::del($id);
513 // reload all blocks to reflect this change on the user interface.
514 $this->ajaxResponse
['reloadBlocks'] = ['#crm-contactinfo-content'];
518 * Handling updating relationship action
520 * @param array $params
523 * @throws \CRM_Core_Exception
525 private function updateAction($params) {
526 list($params, $_) = $this->preparePostProcessParameters($params);
528 civicrm_api3('relationship', 'create', $params);
530 catch (CiviCRM_API3_Exception
$e) {
531 throw new CRM_Core_Exception('Relationship create error ' . $e->getMessage());
534 $this->setMessage(['saved' => TRUE]);
535 return [$params, [$this->_relationshipId
]];
539 * Handling creating relationship action
541 * @param array $params
544 * @throws \CRM_Core_Exception
546 private function createAction($params) {
547 list($params, $primaryContactLetter) = $this->preparePostProcessParameters($params);
549 $outcome = CRM_Contact_BAO_Relationship
::createMultiple($params, $primaryContactLetter);
551 $relationshipIds = $outcome['relationship_ids'];
553 $this->setMessage($outcome);
555 return [$params, $relationshipIds];
559 * Prepares parameters to be used for create/update actions
561 * @param array $values
565 private function preparePostProcessParameters($values) {
567 list($relationshipTypeId, $a, $b) = explode('_', $params['relationship_type_id']);
569 $params['relationship_type_id'] = $relationshipTypeId;
570 $params['contact_id_' . $a] = $this->_contactId
;
572 if (empty($this->_relationshipId
)) {
573 $params['contact_id_' . $b] = explode(',', $params['related_contact_id']);
576 $params['id'] = $this->_relationshipId
;
577 $params['contact_id_' . $b] = $params['related_contact_id'];
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);
584 return [$params, $a];
588 * Updates/Creates relationship notes
590 * @param array $relationshipIds
591 * @param string $note
593 * @throws \CiviCRM_API3_Exception
595 private function saveRelationshipNotes($relationshipIds, $note) {
596 foreach ($relationshipIds as $id) {
599 'entity_table' => 'civicrm_relationship',
602 $existing = civicrm_api3('note', 'get', $noteParams);
603 if (!empty($existing['id'])) {
604 $noteParams['id'] = $existing['id'];
610 $noteParams['note'] = $note;
611 $noteParams['contact_id'] = $this->_contactId
;
613 elseif (!empty($noteParams['id'])) {
617 if (!empty($action)) {
618 civicrm_api3('note', $action, $noteParams);