Prototype for metadata based fields on search screens.
authoreileen <emcnaughton@wikimedia.org>
Thu, 25 Oct 2018 22:40:47 +0000 (11:40 +1300)
committereileen <emcnaughton@wikimedia.org>
Mon, 3 Dec 2018 22:29:10 +0000 (11:29 +1300)
This is a first step towards making search screens more metadata driven. It switches the activity_type_id field
to being metadata-added and adds support for activity_type_id=1 via the url.

The setting of defaults is more like 'normal' forms where they are loaded & can be changed in the UI
rather than 'search methods' where they only work in conjunction with 'force'
(which also still works).

This comes out of my concern that
a) we already have url defaults added badly on this form &
b) we have had more than one PR to add more defaults - eg. https://github.com/civicrm/civicrm-core/pull/12455

So I think we should define how we really want entity data to be used with search forms & move towards it.

Note also that the tpls for Contribution Search and one of the contact search panes have been amended to
be metadata driven. I haven't tried to align with that in this step but it should be down the track

CRM/Activity/BAO/Query.php
CRM/Activity/Form/Search.php
CRM/Core/Form/Search.php

index e964a8aa13d9f088e58ef5c88c408a240f3bd334..fe0ce966b7bef27991dda1906f86be06f38c6f7b 100644 (file)
@@ -433,15 +433,26 @@ class CRM_Activity_BAO_Query {
     return $from;
   }
 
+  /**
+   * Get the metadata for fields to be included on the activity search form.
+   *
+   * @todo ideally this would be a trait included on the activity search & advanced search
+   * rather than a static function.
+   */
+  public static function getSearchFieldMetadata() {
+    $fields = ['activity_type_id'];
+    $metadata = civicrm_api3('Activity', 'getfields', [])['values'];
+    return array_intersect_key($metadata, array_flip($fields));
+  }
+
   /**
    * Add all the elements shared between case activity search and advanced search.
    *
-   * @param CRM_Core_Form $form
+   * @param CRM_Core_Form_Search $form
    */
   public static function buildSearchForm(&$form) {
-    $form->addSelect('activity_type_id',
-      array('entity' => 'activity', 'label' => ts('Activity Type(s)'), 'multiple' => 'multiple', 'option_url' => NULL, 'placeholder' => ts('- any -'))
-    );
+    $form->addSearchFieldMetadata(['Activity' => self::getSearchFieldMetadata()]);
+    $form->addFormFieldsFromMetadata();
 
     CRM_Core_Form_Date::buildDateRange($form, 'activity_date', 1, '_low', '_high', ts('From'), FALSE, FALSE);
     $form->addElement('hidden', 'activity_date_range_error');
index 1433318102fa22ffa696167b7ae5d49abf7de266..f9e4ebfaf059f0b38d9032647bebcb15a82d3c6a 100644 (file)
@@ -69,6 +69,13 @@ class CRM_Activity_Form_Search extends CRM_Core_Form_Search {
    */
   protected $_ssID;
 
+  /**
+   * @return string
+   */
+  public function getDefaultEntity() {
+    return 'Activity';
+  }
+
   /**
    * Processing needed for buildForm and later.
    */
@@ -95,6 +102,8 @@ class CRM_Activity_Form_Search extends CRM_Core_Form_Search {
       if ($this->_force) {
         // If we force the search then merge form values with url values
         // and set submit values to form values.
+        // @todo this is not good security practice. Instead define the fields in metadata & use
+        // getEntityDefaults.
         $this->_formValues = array_merge((array) $this->_formValues, CRM_Utils_Request::exportValues());
         $this->_submitValues = $this->_formValues;
       }
@@ -203,7 +212,6 @@ class CRM_Activity_Form_Search extends CRM_Core_Form_Search {
 
       CRM_Contact_BAO_Query::processSpecialFormValue($this->_formValues, $specialParams, $changeNames);
     }
-
     $this->fixFormValues();
 
     if (isset($this->_ssID) && empty($_POST)) {
@@ -369,6 +377,8 @@ class CRM_Activity_Form_Search extends CRM_Core_Form_Search {
     }
 
     // Enable search activity by custom value
+    // @todo this is not good security practice. Instead define entity fields in metadata &
+    // use getEntity Defaults
     $requestParams = CRM_Utils_Request::exportValues();
     foreach (array_keys($requestParams) as $key) {
       if (substr($key, 0, 7) != 'custom_') {
@@ -396,6 +406,16 @@ class CRM_Activity_Form_Search extends CRM_Core_Form_Search {
     return NULL;
   }
 
+  /**
+   * This virtual function is used to set the default values of various form elements.
+   *
+   * @return array|NULL
+   *   reference to the array of default values
+   */
+  public function setDefaultValues() {
+    return array_merge($this->getEntityDefaults($this->getDefaultEntity()), $this->_formValues);
+  }
+
   /**
    * Return a descriptive name for the page, used in wizard header
    *
@@ -405,4 +425,8 @@ class CRM_Activity_Form_Search extends CRM_Core_Form_Search {
     return ts('Find Activities');
   }
 
+  protected function getEntityMetadata() {
+    return CRM_Activity_BAO_Query::getSearchFieldMetadata();
+  }
+
 }
index 7fbf516ac2e51ef17af68abe1f97b0e98d084d28..4ccd76ccd823c3af419581d6c2d6be62fe6b9d69 100644 (file)
@@ -101,6 +101,27 @@ class CRM_Core_Form_Search extends CRM_Core_Form {
     return $this->_taskList;
   }
 
+  /**
+   * Metadata for fields on the search form.
+   *
+   * @var array
+   */
+  protected $searchFieldMetadata = [];
+
+  /**
+   * @return array
+   */
+  public function getSearchFieldMetadata() {
+    return $this->searchFieldMetadata;
+  }
+
+  /**
+   * @param array $searchFieldMetadata
+   */
+  public function addSearchFieldMetadata($searchFieldMetadata) {
+    $this->searchFieldMetadata = array_merge($this->searchFieldMetadata, $searchFieldMetadata);
+  }
+
   /**
    * Common buildForm tasks required by all searches.
    */
@@ -123,6 +144,65 @@ class CRM_Core_Form_Search extends CRM_Core_Form {
     $this->addTaskMenu($tasks);
   }
 
+  /**
+   * Add any fields described in metadata to the form.
+   *
+   * The goal is to describe all fields in metadata and handle from metadata rather
+   * than existing ad hoc handling.
+   */
+  public function addFormFieldsFromMetadata() {
+    $this->_action = CRM_Core_Action::ADVANCED;
+    foreach ($this->getSearchFieldMetadata() as $entity => $fields) {
+      foreach ($fields as $fieldName => $fieldSpec) {
+        $this->addField($fieldName, ['entity' => $entity]);
+      }
+    }
+  }
+
+  /**
+   * Get the validation rule to apply to a function.
+   *
+   * Alphanumeric is designed to always be safe & for now we just return
+   * that but in future we can use tighter rules for types like int, bool etc.
+   *
+   * @param string $entity
+   * @param string $fieldName
+   *
+   * @return string
+   */
+  protected function getValidationTypeForField($entity, $fieldName) {
+    switch ($this->getSearchFieldMetadata()[$entity][$fieldName]['type']) {
+      case CRM_Utils_Type::T_BOOLEAN:
+        return 'Boolean';
+
+      case CRM_Utils_Type::T_INT:
+        return 'CommaSeparatedIntegers';
+
+      default:
+        return 'Alphanumeric';
+    }
+  }
+
+  /**
+   * Get the defaults for the entity for any fields described in metadata.
+   *
+   * @param string $entity
+   *
+   * @return array
+   */
+  protected function getEntityDefaults($entity) {
+    $defaults = [];
+    foreach ($this->getSearchFieldMetadata()[$entity] as $fieldSpec) {
+      if (empty($_POST[$fieldSpec['name']])) {
+        $value = CRM_Utils_Request::retrieveValue($fieldSpec['name'], $this->getValidationTypeForField($entity, $fieldSpec['name']), FALSE, NULL, 'GET');
+        if ($value !== FALSE) {
+          $defaults[$fieldSpec['name']] = $value;
+        }
+      }
+    }
+    return $defaults;
+  }
+
   /**
    * Add checkboxes for each row plus a master checkbox.
    *