This is an effort at a way to genericise core forms that basically exist to do crud on an entity. We have a
number of fairly straight forward forms of this type in core and, in order to allow extensions to use
custom fields on a range of entities we should support editing them on these core crud forms.
To add custom data support to an entity we need to
a) add the custom data to the form using CRM_Custom_Form_CustomData::addToForm($this);
b) ensure that the entity is saved using an api call not a BAO call
c) add the custom data block to the tpl file - ie {include file="CRM/common/customDataBlock.tpl"}
(the above is possible due to previous work to add support & simplify)
In this PR the adding of the customData is done in the EntityFormTrait, the api is previously converted
and the custom data is included by using a generic tpl to support metadata applied to the form.
By using metadata on the form we can also give extension writers a lot more control over what
is on the form as they can add to, alter, or remove the metadata in a php hook. This
is the crux of this issue https://github.com/civicrm/civicrm-core/pull/12078 & it likewise is
looking to use a generic field template to add fields based on metadata. A key difference between the 2 prs
is that one uses divs & the other a table & that might preclude close sharing of the approach.
Note this PR DOES have the impact of adding translate links to 2 localisable
relationship type fields that did not currently have them - I think this is a good thing?
Example of how to enable custom fields for RelationshipType in an extension.
```
civicrm_api3('OptionValue', 'create', [
'option_group_id' => 'cg_extend_objects',
'name' => 'civicrm_relationship_type',
'label' => ts('Relationship Type'),
'value' => 'RelationshipType',
]);
```
*/
class CRM_Admin_Form_RelationshipType extends CRM_Admin_Form {
+ use CRM_Core_Form_EntityFormTrait;
+
+ /**
+ * Fields for the entity to be assigned to the template.
+ *
+ * Fields may have keys
+ * - name (required to show in tpl from the array)
+ * - description (optional, will appear below the field)
+ * - not-auto-addable - this class will not attempt to add the field using addField.
+ * (this will be automatically set if the field does not have html in it's metadata
+ * or is not a core field on the form's entity).
+ * - help (option) add help to the field - e.g ['id' => 'id-source', 'file' => 'CRM/Contact/Form/Contact']]
+ * - template - use a field specific template to render this field
+ * @var array
+ */
+ protected $entityFields = [];
+
+ /**
+ * Set entity fields to be assigned to the form.
+ */
+ protected function setEntityFields() {
+ $this->entityFields = [
+ 'label_a_b' => [
+ 'name' => 'label_a_b',
+ 'description' => ts("Label for the relationship from Contact A to Contact B. EXAMPLE: Contact A is 'Parent of' Contact B.")
+ ],
+ 'label_b_a' => [
+ 'name' => 'label_b_a',
+ 'description' => ts("Label for the relationship from Contact B to Contact A. EXAMPLE: Contact B is 'Child of' Contact A. You may leave this blank for relationships where the name is the same in both directions (e.g. Spouse).")
+ ],
+ 'description' => ['name' => 'description'],
+ 'contact_types_a' => ['name' => 'contact_types_a', 'not-auto-addable' => TRUE],
+ 'contact_types_b' => ['name' => 'contact_types_b', 'not-auto-addable' => TRUE],
+ 'is_active' => ['name' => 'is_active'],
+ ];
+ }
+
+ /**
+ * Deletion message to be assigned to the form.
+ *
+ * @var string
+ */
+ protected $deleteMessage;
+
/**
* Explicitly declare the entity api name.
*/
return 'RelationshipType';
}
+ /**
+ * Set the delete message.
+ *
+ * We do this from the constructor in order to do a translation.
+ */
+ public function setDeleteMessage() {
+ $this->deleteMessage = ts('WARNING: Deleting this option will result in the loss of all Relationship records of this type.') . ts('This may mean the loss of a substantial amount of data, and the action cannot be undone.') . ts('Do you want to continue?');
+ }
+
/**
* Build the form object.
*/
public function buildQuickForm() {
- parent::buildQuickForm();
- $this->setPageTitle(ts('Relationship Type'));
+ self::buildQuickEntityForm();
if ($this->_action & CRM_Core_Action::DELETE) {
return;
}
- $this->applyFilter('__ALL__', 'trim');
-
- $this->addField('label_a_b');
- $this->addField('label_b_a');
$this->addRule('label_a_b', ts('Label already exists in Database.'),
'objectExists', array('CRM_Contact_DAO_RelationshipType', $this->_id, 'label_a_b')
);
'objectExists', array('CRM_Contact_DAO_RelationshipType', $this->_id, 'label_b_a')
);
- $this->addField('description');
-
$contactTypes = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, TRUE, '__');
// add select for contact type
) + $contactTypes
);
- $this->addField('is_active');
-
//only selected field should be allow for edit, CRM-4888
if ($this->_id &&
CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', $this->_id, 'is_reserved')
$this->freeze();
}
- $this->assign('relationship_type_id', $this->_id);
-
}
/**
$this->addClass(CRM_Utils_System::getClassName($this));
$this->assign('snippet', CRM_Utils_Array::value('snippet', $_GET));
+ $this->setTranslatedFields();
}
+ /**
+ * Set translated fields.
+ *
+ * This function is called from the class constructor, allowing us to set
+ * fields on the class that can't be set as properties due to need for
+ * translation or other non-input specific handling.
+ */
+ protected function setTranslatedFields() {}
+
/**
* Add one or more css classes to the form.
*
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2018
+ */
+
+trait CRM_Core_Form_EntityFormTrait {
+ /**
+ * Get entity fields for the entity to be added to the form.
+ *
+ * @var array
+ */
+ public function getEntityFields() {
+ return $this->entityFields;
+ }
+
+ /**
+ * Explicitly declare the form context.
+ */
+ public function getDefaultContext() {
+ return 'create';
+ }
+
+ /**
+ * Get entity fields for the entity to be added to the form.
+ *
+ * @var array
+ */
+ public function getDeleteMessage() {
+ return $this->deleteMessage;
+ }
+
+ /**
+ * Get the entity id being edited.
+ *
+ * @return int|null
+ */
+ public function getEntityId() {
+ return $this->_id;
+ }
+ /**
+ * If the custom data is in the submitted data (eg. added via ajax loaded form) add to form.
+ */
+ public function addCustomDataToForm() {
+ $customisableEntities = CRM_Core_SelectValues::customGroupExtends();
+ if (isset($customisableEntities[$this->getDefaultEntity()])) {
+ CRM_Custom_Form_CustomData::addToForm($this);
+ }
+ }
+
+ /**
+ * Build the form object.
+ */
+ public function buildQuickEntityForm() {
+ if ($this->_action & CRM_Core_Action::DELETE) {
+ $this->buildDeleteForm();
+ return;
+ }
+ $this->applyFilter('__ALL__', 'trim');
+ $this->addEntityFieldsToTemplate();
+ $this->assign('entityFields', $this->entityFields);
+ $this->assign('entityID', $this->getEntityId());
+ $this->assign('entityInClassFormat', strtolower(str_replace('_', '-', $this->getDefaultEntity())));
+ $this->assign('entityTable', CRM_Core_DAO_AllCoreTables::getTableForClass(CRM_Core_DAO_AllCoreTables::getFullName($this->getDefaultEntity())));
+ $this->addCustomDataToForm();
+ $this->addFormButtons();
+ }
+
+ /**
+ * Build the form for any deletion.
+ */
+ protected function buildDeleteForm() {
+ $this->assign('deleteMessage', $this->getDeleteMessage());
+ $this->addFormButtons();
+ }
+
+ /**
+ * Add relevant buttons to the form.
+ */
+ protected function addFormButtons() {
+ if ($this->_action & CRM_Core_Action::VIEW || $this->_action & CRM_Core_Action::PREVIEW) {
+ $this->addButtons(array(
+ array(
+ 'type' => 'cancel',
+ 'name' => ts('Done'),
+ 'isDefault' => TRUE,
+ ),
+ )
+ );
+ }
+ else {
+ $this->addButtons(array(
+ array(
+ 'type' => 'next',
+ 'name' => $this->_action & CRM_Core_Action::DELETE ? ts('Delete') : ts('Save'),
+ 'isDefault' => TRUE,
+ ),
+ array(
+ 'type' => 'cancel',
+ 'name' => ts('Cancel'),
+ ),
+ )
+ );
+ }
+ }
+
+ /**
+ * Set translated fields.
+ *
+ * This function is called from the class constructor, allowing us to set
+ * fields on the class that can't be set as properties due to need for
+ * translation or other non-input specific handling.
+ */
+ protected function setTranslatedFields() {
+ $this->setEntityFields();
+ $this->setDeleteMessage();
+ $metadata = civicrm_api3($this->getDefaultEntity(), 'getfields', ['action' => 'create']);
+ $this->metadata = $metadata['values'];
+ foreach ($this->metadata as $fieldName => $spec) {
+ if (isset($this->entityFields[$fieldName])) {
+ if ($spec['localizable']) {
+ $this->entityFields[$fieldName]['is_add_translate_dialog'] = TRUE;
+ }
+ if (empty($spec['html'])) {
+ $this->entityFields[$fieldName]['not-auto-addable'] = TRUE;
+ }
+ }
+ }
+ }
+
+ /**
+ * Add defined entity field to template.
+ */
+ protected function addEntityFieldsToTemplate() {
+ foreach ($this->getEntityFields() as $fieldSpec) {
+ if (empty($fieldSpec['not-auto-addable'])) {
+ $this->addField($fieldSpec['name']);
+ }
+ }
+ }
+
+}
+--------------------------------------------------------------------+
*}
{* this template is used for adding/editing relationship types *}
-<div class="crm-block crm-form-block crm-relationship-type-form-block">
- <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="top"}</div>
- {if $action eq 8}
- <div class="messages status no-popup">
- <div class="icon inform-icon"></div>
- {ts}WARNING: Deleting this option will result in the loss of all Relationship records of this type.{/ts} {ts}This may mean the loss of a substantial amount of data, and the action cannot be undone.{/ts} {ts}Do you want to continue?{/ts}
-
-
- </div>
- {else}
- <table class="form-layout-compressed">
- <tr class="crm-relationship-type-form-block-label_a_b">
- <td class="label">{$form.label_a_b.label} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_relationship_type' field='label_a_b' id=$relationship_type_id}{/if}</td>
- <td>{$form.label_a_b.html}<br />
- <span class="description">{ts}Label for the relationship from Contact A to Contact B. EXAMPLE: Contact A is 'Parent of' Contact B.{/ts}</span></td>
- </tr>
- <tr class="crm-relationship-type-form-block-label_b_a">
- <td class="label">{$form.label_b_a.label} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_relationship_type' field='label_b_a' id=$relationship_type_id}{/if}</td>
- <td>{$form.label_b_a.html}<br />
- <span class="description">{ts}Label for the relationship from Contact B to Contact A. EXAMPLE: Contact B is 'Child of' Contact A. You may leave this blank for relationships where the name is the same in both directions (e.g. Spouse).{/ts}</span></td>
- </tr>
- <tr class="crm-relationship-type-form-block-contact_types_a">
- <td class="label">{$form.contact_types_a.label}</td>
- <td>{$form.contact_types_a.html}</td>
- </tr>
- <tr class="crm-relationship-type-form-block-contact_types_b">
- <td class="label">{$form.contact_types_b.label}</td>
- <td>{$form.contact_types_b.html}</td>
- </tr>
- <tr class="crm-relationship-type-form-block-description">
- <td class="label">{$form.description.label} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_relationship_type' field='description' id=$relationship_type_id}{/if}</td>
- <td>{$form.description.html}</td>
- </tr>
- <tr class="crm-relationship-type-form-block-is_active">
- <td class="label">{$form.is_active.label}</td>
- <td>{$form.is_active.html}</td>
- </tr>
- </table>
- {/if}
- <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
-</div>
+{include file="CRM/Core/Form/EntityForm.tpl"}
--- /dev/null
+{*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+*}
+{* this template is used for adding/editing entities *}
+<div class="crm-block crm-form-block crm-{$entityInClassFormat}-form-block">
+ <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="top"}</div>
+ {if $action eq 8}
+ <div class="messages status no-popup">
+ <div class="icon inform-icon"></div>
+ {$deleteMessage|escape}
+ </div>
+ {else}
+ <table class="form-layout-compressed">
+ {foreach from=$entityFields item=fieldSpec}
+ {assign var=fieldName value=$fieldSpec.name}
+ <tr class="crm-{$entityInClassFormat}-form-block-{$fieldName}">
+ {include file="CRM/Core/Form/Field.tpl"}
+ </tr>
+ {/foreach}
+ </table>
+ {include file="CRM/common/customDataBlock.tpl"}
+ {/if}
+ <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
+</div>
--- /dev/null
+{*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+*}
+{if $fieldSpec.template}
+ {include file=$fieldSpec.template}
+{else}
+ <td class="label">{$form.$fieldName.label}
+ {if $fieldSpec.help}{assign var=help value=$fieldSpec.help}{capture assign=helpFile}{if $fieldSpec.help}
+ {$fieldSpec.help}
+ {else}''{/if}
+ {/capture}{help id=$help.id file=$help.file}{/if}
+ {if $action == 2 && $fieldSpec.is_add_translate_dialog}{include file='CRM/Core/I18n/Dialog.tpl' table=$entityTable field=$fieldName id=$entityID}{/if}
+ </td>
+ <td>{if $form.$fieldName.html}{if $fieldSpec.formatter === 'crmMoney'}{$form.$fieldName.html|crmMoney}{else}{$form.$fieldName.html}{/if}{else}{$fieldSpec.place_holder}{/if}<br />
+ {if $fieldSpec.description}<span class="description">{$fieldSpec.description}</span>{/if}
+ </td>
+{/if}