Support custom data on the Relationship Type form IF enabled via extension.
authoreileen <emcnaughton@wikimedia.org>
Mon, 14 May 2018 04:32:41 +0000 (16:32 +1200)
committereileen <emcnaughton@wikimedia.org>
Tue, 15 May 2018 07:12:21 +0000 (19:12 +1200)
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',
      ]);
```

CRM/Admin/Form/RelationshipType.php
CRM/Core/Form.php
CRM/Core/Form/EntityFormTrait.php [new file with mode: 0644]
templates/CRM/Admin/Form/RelationshipType.tpl
templates/CRM/Core/Form/EntityForm.tpl [new file with mode: 0644]
templates/CRM/Core/Form/Field.tpl [new file with mode: 0644]

index 2c07ed88634e5bc95591839123504d5dd8d21109..ed13ef49a3667a6aa5d8b426e84a0acf552ad911 100644 (file)
  */
 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.
    */
@@ -43,21 +87,25 @@ class CRM_Admin_Form_RelationshipType extends CRM_Admin_Form {
     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')
     );
@@ -65,8 +113,6 @@ class CRM_Admin_Form_RelationshipType extends CRM_Admin_Form {
       '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
@@ -81,8 +127,6 @@ class CRM_Admin_Form_RelationshipType extends CRM_Admin_Form {
       ) + $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')
@@ -96,8 +140,6 @@ class CRM_Admin_Form_RelationshipType extends CRM_Admin_Form {
       $this->freeze();
     }
 
-    $this->assign('relationship_type_id', $this->_id);
-
   }
 
   /**
index 58c332f48bbf7c06634c62e64059b2a490cc3859..15a0cd245f60b7c0a0f204c7ca3fe2636e0cf668 100644 (file)
@@ -269,8 +269,18 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     $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.
    *
diff --git a/CRM/Core/Form/EntityFormTrait.php b/CRM/Core/Form/EntityFormTrait.php
new file mode 100644 (file)
index 0000000..c102c94
--- /dev/null
@@ -0,0 +1,169 @@
+<?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']);
+      }
+    }
+  }
+
+}
index 63b86f289e4e4c4b833318d9748ac022a610bb0a..ffd91f73d3785e4a189023d514bca20ced6fd134 100644 (file)
  +--------------------------------------------------------------------+
 *}
 {* 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"}
diff --git a/templates/CRM/Core/Form/EntityForm.tpl b/templates/CRM/Core/Form/EntityForm.tpl
new file mode 100644 (file)
index 0000000..c59b0d2
--- /dev/null
@@ -0,0 +1,46 @@
+{*
+ +--------------------------------------------------------------------+
+ | 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>
diff --git a/templates/CRM/Core/Form/Field.tpl b/templates/CRM/Core/Form/Field.tpl
new file mode 100644 (file)
index 0000000..79d68bd
--- /dev/null
@@ -0,0 +1,39 @@
+{*
+ +--------------------------------------------------------------------+
+ | 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}