diff --git a/CRM/Contact/BAO/Relationship.php b/CRM/Contact/BAO/Relationship.php index b2ac947046f2..3acb4656b422 100644 --- a/CRM/Contact/BAO/Relationship.php +++ b/CRM/Contact/BAO/Relationship.php @@ -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; @@ -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; + } + } diff --git a/CRM/Contact/Form/Relationship.php b/CRM/Contact/Form/Relationship.php index e90727bb0715..ff0b61bc1e67 100644 --- a/CRM/Contact/Form/Relationship.php +++ b/CRM/Contact/Form/Relationship.php @@ -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)); + $this->assign('contactTypes', CRM_Contact_BAO_ContactType::contactTypeInfo(TRUE)); foreach ($this->_allRelationshipNames as $id => $vals) { if ($vals['name_a_b'] === 'Employee of') { @@ -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); diff --git a/CRM/Core/Form.php b/CRM/Core/Form.php index c71388f5360f..29cc6295c791 100644 --- a/CRM/Core/Form.php +++ b/CRM/Core/Form.php @@ -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; } diff --git a/api/v3/Generic.php b/api/v3/Generic.php index b6d0bb1cac88..848d4731bc91 100644 --- a/api/v3/Generic.php +++ b/api/v3/Generic.php @@ -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); + } } /** diff --git a/api/v3/Relationship.php b/api/v3/Relationship.php index bab339b835ac..22cd6ddf0a30 100644 --- a/api/v3/Relationship.php +++ b/api/v3/Relationship.php @@ -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 + ]; +} diff --git a/js/crm.optionEdit.js b/js/crm.optionEdit.js index 1b1338ce00a7..20e01b7afdcb 100644 --- a/js/crm.optionEdit.js +++ b/js/crm.optionEdit.js @@ -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); }); diff --git a/templates/CRM/Contact/Form/Relationship.tpl b/templates/CRM/Contact/Form/Relationship.tpl index 28f496c22a7d..36d29a4daa89 100644 --- a/templates/CRM/Contact/Form/Relationship.tpl +++ b/templates/CRM/Contact/Form/Relationship.tpl @@ -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 @@ -190,7 +268,7 @@ CRM.buildCustomData('Relationship', rType); } - }).change(); + } }); {/literal} diff --git a/templates/CRM/common/enableDisableApi.tpl b/templates/CRM/common/enableDisableApi.tpl index cb359af23c72..f8ea2b5d89b4 100644 --- a/templates/CRM/common/enableDisableApi.tpl +++ b/templates/CRM/common/enableDisableApi.tpl @@ -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); }