Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRM-21849: Inline Relationship Type Edit #11853

Merged
71 changes: 70 additions & 1 deletion CRM/Contact/BAO/Relationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ public static function getContactRelationshipType(
$relationship->id = $relationshipId;
if ($relationship->find(TRUE)) {
$contact = new CRM_Contact_DAO_Contact();
$contact->id = ($relationship->contact_id_a === $contactId) ? $relationship->contact_id_b : $relationship->contact_id_a;
$contact->id = ($relationship->contact_id_a == $contactId) ? $relationship->contact_id_b : $relationship->contact_id_a;

if ($contact->find(TRUE)) {
$otherContactType = $contact->contact_type;
Expand Down Expand Up @@ -2149,4 +2149,73 @@ public static function getContactRelationshipSelector(&$params) {
return $relationshipsDT;
}

/**
* @inheritdoc
*/
public static function buildOptions($fieldName, $context = NULL, $props = array()) {
if ($fieldName === 'relationship_type_id') {
return self::buildRelationshipTypeOptions($props);
}

return parent::buildOptions($fieldName, $context, $props);
}

/**
* Builds a list of options available for relationship types
*
* @param array $params
* - contact_type: Limits by contact type on the "A" side
* - relationship_id: Used to find the value for contact type for "B" side.
* If contact_a matches provided contact_id then type of contact_b will
* be used. Otherwise uses type of contact_a. Must be used with contact_id
* - contact_id: Limits by contact types of this contact on the "A" side
* - is_form: Returns array with keys indexed for use in a quickform
* - relationship_direction: For relationship types with duplicate names
* on both sides, defines which option should be returned, a_b or b_a
*
* @return array
*/
public static function buildRelationshipTypeOptions($params = array()) {
$contactId = CRM_Utils_Array::value('contact_id', $params);
$direction = CRM_Utils_Array::value('relationship_direction', $params, 'a_b');
$relationshipId = CRM_Utils_Array::value('relationship_id', $params);
$contactType = CRM_Utils_Array::value('contact_type', $params);
$isForm = CRM_Utils_Array::value('is_form', $params);
$showAll = FALSE;

// getContactRelationshipType will return an empty set if these are not set
if (!$contactId && !$relationshipId && !$contactType) {
$showAll = TRUE;
}

$labels = self::getContactRelationshipType(
$contactId,
$direction,
$relationshipId,
$contactType,
$showAll,
'label'
);

if ($isForm) {
return $labels;
}

$names = self::getContactRelationshipType(
$contactId,
$direction,
$relationshipId,
$contactType,
$showAll,
'name'
);

// ensure $names contains only entries in $labels
$names = array_intersect_key($names, $labels);

$nameToLabels = array_combine($names, $labels);

return $nameToLabels;
}

}
20 changes: 17 additions & 3 deletions CRM/Contact/Form/Relationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,7 @@ public function buildQuickForm() {
// Select list
$relationshipList = CRM_Contact_BAO_Relationship::getContactRelationshipType($this->_contactId, $this->_rtype, $this->_relationshipId);

// Metadata needed on clientside
$this->assign('relationshipData', self::getRelationshipTypeMetadata($relationshipList));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the function getRelationshipTypeMetadata is no longer used then it can be removed. I agree this can all be done from the frontend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to clean it up to avoid having code that does the same thing in JS and PHP, but it's still used in one other place. My JS skills are very limited, but if you think it's best that we remove this function and provide some common service to fetch this data then I can give that a shot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, didn't realize it was used elsewhere. I don't think that's a priority.

$this->assign('contactTypes', CRM_Contact_BAO_ContactType::contactTypeInfo(TRUE));

foreach ($this->_allRelationshipNames as $id => $vals) {
if ($vals['name_a_b'] === 'Employee of') {
Expand All @@ -309,7 +308,22 @@ public function buildQuickForm() {
}
}

$this->addField('relationship_type_id', array('options' => array('' => ts('- select -')) + $relationshipList, 'class' => 'huge', 'placeholder' => '- select -'), TRUE);
$this->addField(
'relationship_type_id',
array(
'options' => array('' => ts('- select -')) + $relationshipList,
'class' => 'huge',
'placeholder' => '- select -',
'option_url' => 'civicrm/admin/reltype',
'option_context' => array(
'contact_id' => $this->_contactId,
'relationship_direction' => $this->_rtype,
'relationship_id' => $this->_relationshipId,
'is_form' => TRUE,
),
),
TRUE
);

$label = $this->_action & CRM_Core_Action::ADD ? ts('Contact(s)') : ts('Contact');
$contactField = $this->addField('related_contact_id', array('label' => $label, 'name' => 'contact_id_b', 'multiple' => TRUE, 'create' => TRUE), TRUE);
Expand Down
6 changes: 6 additions & 0 deletions CRM/Core/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,12 @@ public function &add(
}
}

// Add context for the editing of option groups
if (isset($extra['option_context'])) {
$context = json_encode($extra['option_context']);
$element->setAttribute('data-option-edit-context', $context);
}

return $element;
}

Expand Down
7 changes: 7 additions & 0 deletions api/v3/Generic.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,13 @@ function _civicrm_api3_generic_getoptions_spec(&$params, $apiRequest) {
}
}
}

$entityName = _civicrm_api_get_entity_name_from_camel($apiRequest['entity']);
$getOptionsSpecFunction = '_civicrm_api3_' . $entityName . '_getoptions_spec';

if (function_exists($getOptionsSpecFunction)) {
$getOptionsSpecFunction($params);
}
}

/**
Expand Down
50 changes: 50 additions & 0 deletions api/v3/Relationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,53 @@ function civicrm_api3_relationship_setvalue($params) {
}
return $result;
}

function _civicrm_api3_relationship_getoptions_spec(&$params) {
$params['field']['options']['relationship_type_id'] = ts('Relationship Type ID');

// Add parameters for limiting relationship type ID
$relationshipTypePrefix = ts('(For relationship_type_id only) ');
$params['contact_id'] = [
'title' => ts('Contact ID'),
'description' => $relationshipTypePrefix . ts('Limits options to those'
. ' available to give contact'),
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Contact_DAO_Contact',
'FKApiName' => 'Contact',
];
$params['relationship_direction'] = [
'title' => ts('Relationship Direction'),
'description' => $relationshipTypePrefix . ts('For relationships where the '
. 'name is the same for both sides (i.e. "Spouse Of") show the option '
. 'from "A" (origin) side or "B" (target) side of the relationship?'),
'type' => CRM_Utils_Type::T_STRING,
'options' => ['a_b' => 'a_b', 'b_a' => 'b_a'],
'api.default' => 'a_b',
];
$params['relationship_id'] = [
'title' => ts('Reference Relationship ID'),
'description' => $relationshipTypePrefix . ts('If provided alongside '
. 'contact ID it will be used to establish the contact type of the "B" '
. 'side of the relationship and limit options based on it. If the '
. 'provided contact ID does not match the "A" side of this relationship '
. 'then the "A" side of this relationship will be used to limit options'),
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Contact_DAO_Relationship',
'FKApiName' => 'Relationship',
];
$contactTypes = CRM_Contact_BAO_ContactType::contactTypes();
$params['contact_type'] = [
'title' => ts('Contact Type'),
'description' => $relationshipTypePrefix . ts('Limits options to those '
. 'available to this contact type. Overridden by the contact type of '
. 'contact ID (if provided)'),
'type' => CRM_Utils_Type::T_STRING,
'options' => array_combine($contactTypes, $contactTypes),
];
$params['is_form'] = [
'title' => ts('Is Form?'),
'description' => $relationshipTypePrefix . ts('Formats the options for use'
. ' in a form if true. The format is <id>_a_b => <label>'),
'type' => CRM_Utils_Type::T_BOOLEAN
];
}
7 changes: 5 additions & 2 deletions js/crm.optionEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ jQuery(function($) {
*/
function rebuildOptions($existing, rebuilder) {
if ($existing.data('api-entity') && $existing.data('api-field')) {
CRM.api3($existing.data('api-entity'), 'getoptions', {
var params = {
sequential: 1,
field: $existing.data('api-field')
})
};
$.extend(params, $existing.data('option-edit-context'));

CRM.api3($existing.data('api-entity'), 'getoptions', params)
.done(function(data) {
rebuilder($existing, data.values);
});
Expand Down
86 changes: 82 additions & 4 deletions templates/CRM/Contact/Form/Relationship.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,88 @@
CRM.$(function($) {
var
$form = $("form.{/literal}{$form.formClass}{literal}"),
relationshipData = {/literal}{$relationshipData|@json_encode}{literal};
$('[name=relationship_type_id]', $form).change(function() {
$relationshipTypeSelect = $('[name=relationship_type_id]', $form),
relationshipData = {},
contactTypes = {/literal}{$contactTypes|@json_encode}{literal};

(function init () {
// Refresh options if relationship types were edited
$('body').on('crmOptionsEdited', 'a.crm-option-edit-link', refreshRelationshipData);
// Initial load and trigger change on select
refreshRelationshipData().done(function() {
$relationshipTypeSelect.change();
});
$relationshipTypeSelect.change(function() {
var $select = $(this);

// ensure we have relationship data before changing anything
getRelationshipData().then(function() {
updateSelect($select);
})
});
})();

/**
* Fetch contact types and reset relationship data
*/
function refreshRelationshipData() {
// reset
relationshipData = {};

return getRelationshipData();
}

/**
* Fetches the relationship data using latest relationship types
*/
function getRelationshipData() {
var defer = $.Deferred();

if (!$.isEmptyObject(relationshipData)) {
defer.resolve(relationshipData);
}

CRM.api3("RelationshipType", "get", {"options": {"limit":0}})
.done(function (data) {
$.each(data.values, function (key, relType) {
// Loop over the suffixes for a relationship type
$.each(["a", "b"], function (index, suffix) {
var subtype = relType["contact_subtype_" + suffix];
var type = subtype || relType["contact_type_" + suffix];
var label = getContactTypeLabel(type) || "Contact";
label = label.toLowerCase();
relType["placeholder_" + suffix] = "- select " + label + " -";
});

relationshipData[relType["id"]] = relType;
});

defer.resolve(relationshipData);
});

return defer.promise();
}

/**
* Gets a contact type label based on a provided name
* @param {String} name - the name of the contact type
*/
function getContactTypeLabel(name) {
var label = "";

$.each(contactTypes, function(index, contactType) {
if (contactType.name === name) {
label = contactType.label;
return false;
}
});

return label;
}

function updateSelect($select) {
var
val = $(this).val(),
val = $select.val(),
$contactField = $('#related_contact_id[type=text]', $form);
if (!val && $contactField.length) {
$contactField
Expand Down Expand Up @@ -190,7 +268,7 @@

CRM.buildCustomData('Relationship', rType);
}
}).change();
}
});
{/literal}
</script>
Expand Down
8 changes: 7 additions & 1 deletion templates/CRM/common/enableDisableApi.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@
}

function refresh() {
$a.trigger('crmPopupFormSuccess');
// the opposite of the current status based on row class
var newStatus = $row.hasClass('disabled');
$a.trigger('crmPopupFormSuccess', {
'entity': info.entity,
'id': info.id,
'enabled': newStatus
});
CRM.refreshParent($row);
}

Expand Down
Loading