CRM-13966 - Add method to Core_Form for contactRef fields
[civicrm-core.git] / CRM / Core / Form.php
index 5e6f2c78d347653cad1174b3d155787ec0a18062..c604f97c563a6917c2acd0bd71824d33aedef71e 100644 (file)
@@ -98,6 +98,21 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
    */
   static protected $_template;
 
+  /**
+   * What to return to the client if in ajax mode (snippet=json)
+   *
+   * @var array
+   */
+  public $ajaxResponse = array();
+
+  /**
+   * Stores info about reference fields for preprocessing
+   * Public so that hooks can access it
+   *
+   * @var array
+   */
+  public $entityReferenceFields = array();
+
   /**
    * constants for attributes for various form elements
    * attempt to standardize on the number of variations that we
@@ -159,6 +174,8 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     if (!isset(self::$_template)) {
       self::$_template = CRM_Core_Smarty::singleton();
     }
+
+    $this->assign('snippet', (int) CRM_Utils_Array::value('snippet', $_REQUEST));
   }
 
   static function generateID() {
@@ -199,7 +216,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
    *               These are not default values
    * @param bool   is this a required field
    *
-   * @return object    html element, could be an error object
+   * @return HTML_QuickForm_Element could be an error object
    * @access public
    *
    */
@@ -259,8 +276,17 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
    */
   function mainProcess() {
     $this->postProcess();
-
     $this->postProcessHook();
+
+    // Respond with JSON if in AJAX context (also support legacy value '6')
+    if (!empty($_REQUEST['snippet']) && in_array($_REQUEST['snippet'], array(CRM_Core_Smarty::PRINT_JSON, 6))) {
+      $this->ajaxResponse['buttonName'] = str_replace('_qf_' . $this->getAttribute('id') . '_', '', $this->controller->getButtonName());
+      $this->ajaxResponse['action'] = $this->_action;
+      if (isset($this->_id) || isset($this->id)) {
+        $this->ajaxResponse['id'] = isset($this->id) ? $this->id : $this->_id;
+      }
+      CRM_Core_Page_AJAX::returnJsonResponse($this->ajaxResponse);
+    }
   }
 
   /**
@@ -381,6 +407,8 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     // the user can do both the form and set default values with this hook
     CRM_Utils_Hook::buildForm(get_class($this), $this);
 
+    $this->preprocessReferenceFields();
+
     $this->addRules();
   }
 
@@ -418,7 +446,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
         $prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
       }
       else {
-        if (CRM_Utils_Array::value('subName', $button)) {
+        if (!empty($button['subName'])) {
           $buttonName = $this->getButtonName($button['type'], $button['subName']);
         }
         else {
@@ -431,7 +459,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
         }
         $prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
       }
-      if (CRM_Utils_Array::value('isDefault', $button)) {
+      if (!empty($button['isDefault'])) {
         $this->setDefaultAction($button['type']);
       }
 
@@ -943,6 +971,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     $this->assign('editor', $editor);
 
     // include wysiwyg editor js files
+    // FIXME: This code does not make any sense
     $includeWysiwygEditor = FALSE;
     $includeWysiwygEditor = $this->get('includeWysiwygEditor');
     if (!$includeWysiwygEditor) {
@@ -972,98 +1001,6 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     }
   }
 
-  function buildAddressBlock($locationId, $title, $phone,
-    $alternatePhone = NULL, $addressRequired = NULL,
-    $phoneRequired  = NULL, $altPhoneRequired = NULL,
-    $locationName   = NULL
-  ) {
-    if (!$locationName) {
-      $locationName = "location";
-    }
-
-    $config = CRM_Core_Config::singleton();
-    $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address');
-
-    $location[$locationId]['address']['street_address'] = $this->addElement('text', "{$locationName}[$locationId][address][street_address]", $title,
-      $attributes['street_address']
-    );
-    if ($addressRequired) {
-      $this->addRule("{$locationName}[$locationId][address][street_address]", ts("Please enter the Street Address for %1.", array(1 => $title)), 'required');
-    }
-
-    $location[$locationId]['address']['supplemental_address_1'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_1]", ts('Supplemental Address 1'),
-      $attributes['supplemental_address_1']
-    );
-    $location[$locationId]['address']['supplemental_address_2'] = $this->addElement('text', "{$locationName}[$locationId][address][supplemental_address_2]", ts('Supplemental Address 2'),
-      $attributes['supplemental_address_2']
-    );
-
-    $location[$locationId]['address']['city'] = $this->addElement('text', "{$locationName}[$locationId][address][city]", ts('City'),
-      $attributes['city']
-    );
-    if ($addressRequired) {
-      $this->addRule("{$locationName}[$locationId][address][city]", ts("Please enter the City for %1.", array(1 => $title)), 'required');
-    }
-
-    $location[$locationId]['address']['postal_code'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code]", ts('Zip / Postal Code'),
-      $attributes['postal_code']
-    );
-    if ($addressRequired) {
-      $this->addRule("{$locationName}[$locationId][address][postal_code]", ts("Please enter the Zip/Postal Code for %1.", array(1 => $title)), 'required');
-    }
-
-    $location[$locationId]['address']['postal_code_suffix'] = $this->addElement('text', "{$locationName}[$locationId][address][postal_code_suffix]", ts('Add-on Code'),
-      array('size' => 4, 'maxlength' => 12)
-    );
-    $this->addRule("{$locationName}[$locationId][address][postal_code_suffix]", ts('Zip-Plus not valid.'), 'positiveInteger');
-
-    if ($config->includeCounty) {
-      $location[$locationId]['address']['county_id'] = $this->addElement('select', "{$locationName}[$locationId][address][county_id]", ts('County'),
-        array('' => ts('- select -')) + CRM_Core_PseudoConstant::county()
-      );
-    }
-
-    $location[$locationId]['address']['state_province_id'] = $this->addElement('select', "{$locationName}[$locationId][address][state_province_id]", ts('State / Province'),
-      array('' => ts('- select -')) + CRM_Core_PseudoConstant::stateProvince()
-    );
-
-    $location[$locationId]['address']['country_id'] = $this->addElement('select', "{$locationName}[$locationId][address][country_id]", ts('Country'),
-      array('' => ts('- select -')) + CRM_Core_PseudoConstant::country()
-    );
-    if ($addressRequired) {
-      $this->addRule("{$locationName}[$locationId][address][country_id]", ts("Please select the Country for %1.", array(1 => $title)), 'required');
-    }
-
-
-    if ($phone) {
-      $location[$locationId]['phone'][1]['phone'] = $this->addElement('text',
-        "{$locationName}[$locationId][phone][1][phone]",
-        $phone,
-        CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
-          'phone'
-        )
-      );
-      if ($phoneRequired) {
-        $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a value for %1', array(1 => $phone)), 'required');
-      }
-      $this->addRule("{$locationName}[$locationId][phone][1][phone]", ts('Please enter a valid number for %1', array(1 => $phone)), 'phone');
-    }
-
-    if ($alternatePhone) {
-      $location[$locationId]['phone'][2]['phone'] = $this->addElement('text',
-        "{$locationName}[$locationId][phone][2][phone]",
-        $alternatePhone,
-        CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
-          'phone'
-        )
-      );
-      if ($alternatePhoneRequired) {
-        $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a value for %1', array(1 => $alternatePhone)), 'required');
-      }
-      $this->addRule("{$locationName}[$locationId][phone][2][phone]", ts('Please enter a valid number for %1', array(1 => $alternatePhone)), 'phone');
-    }
-  }
-
   public function getRootTitle() {
     return NULL;
   }
@@ -1130,7 +1067,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
    *
    */
   function addDate($name, $label, $required = FALSE, $attributes = NULL) {
-    if (CRM_Utils_Array::value('formatType', $attributes)) {
+    if (!empty($attributes['formatType'])) {
       // get actual format
       $params = array('name' => $attributes['formatType']);
       $values = array();
@@ -1138,7 +1075,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
       // cache date information
       static $dateFormat;
       $key = "dateFormat_" . str_replace(' ', '_', $attributes['formatType']);
-      if (!CRM_Utils_Array::value($key, $dateFormat)) {
+      if (empty($dateFormat[$key])) {
         CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
         $dateFormat[$key] = $values;
       }
@@ -1150,7 +1087,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
         $attributes['format'] = $values['date_format'];
       }
 
-      if (CRM_Utils_Array::value('time_format', $values)) {
+      if (!empty($values['time_format'])) {
         $attributes['timeFormat'] = $values['time_format'];
       }
       $attributes['startOffset'] = $values['start'];
@@ -1158,7 +1095,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     }
 
     $config = CRM_Core_Config::singleton();
-    if (!CRM_Utils_Array::value('format', $attributes)) {
+    if (empty($attributes['format'])) {
       $attributes['format'] = $config->dateInputFormat;
     }
 
@@ -1172,9 +1109,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
 
     $this->add('text', $name, $label, $attributes);
 
-    if (CRM_Utils_Array::value('addTime', $attributes) ||
-      CRM_Utils_Array::value('timeFormat', $attributes)
-    ) {
+    if (!empty($attributes['addTime']) || !empty($attributes['timeFormat'])) {
 
       if (!isset($attributes['timeFormat'])) {
         $timeFormat = $config->timeInputFormat;
@@ -1203,7 +1138,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
 
     if ($required) {
       $this->addRule($name, ts('Please select %1', array(1 => $label)), 'required');
-      if (CRM_Utils_Array::value('addTime', $attributes) && CRM_Utils_Array::value('addTimeRequired', $attributes)) {
+      if (!empty($attributes['addTime']) && !empty($attributes['addTimeRequired'])) {
         $this->addRule($elementName, ts('Please enter a time.'), 'required');
       }
     }
@@ -1271,6 +1206,88 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     $this->setDefaults(array($name => $defaultCurrency));
   }
 
+  /**
+   * Create a single or multiple contact ref field
+   * @param string $name
+   * @param string $label
+   * @param array $props mix of html and widget properties, including:
+   *   - required
+   *   - select - params to give to select2, notably "multiple"
+   *   - api - array of data for the api, keys include "entity", "action", "params", "search", "key", "label"
+   * @return HTML_QuickForm_Element
+   */
+  function addContactRef($name, $label, $props = array(), $required = FALSE) {
+    $props['class'] = isset($props['class']) ? $props['class'] . ' crm-select2' : 'crm-select2';
+
+    $props['select'] = CRM_Utils_Array::value('select', $props, array()) + array(
+      'minimumInputLength' => 1,
+      'multiple' => !empty($props['multiple']),
+      'placeholder' => CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select -') : ts('- none -')),
+      'allowClear' => !$required,
+      // Disabled pending https://github.com/ivaynberg/select2/pull/2092
+      //'formatInputTooShort' => ts('Start typing a name or email address...'),
+      //'formatNoMatches' => ts('No contacts found.'),
+    );
+
+    $props['api'] = CRM_Utils_Array::value('api', $props, array()) + array(
+      'entity' => 'contact',
+      'action' => 'getquick',
+      'search' => 'name',
+      'label' => 'data',
+      'key' => 'id',
+    );
+
+    $this->entityReferenceFields[$name] = $props;
+    $this->formatReferenceFieldAttributes($props);
+    return $this->add('text', $name, $label, $props, $required);
+  }
+
+  /**
+   * @param $props
+   */
+  private function formatReferenceFieldAttributes(&$props) {
+    $props['data-select-params'] = json_encode($props['select']);
+    $props['data-api-params'] = json_encode($props['api']);
+    CRM_Utils_Array::remove($props, 'multiple', 'select', 'api');
+  }
+
+  private function preprocessReferenceFields() {
+    foreach ($this->entityReferenceFields as $name => $props) {
+      $val = $this->getElementValue($name);
+      $field = $this->getElement($name);
+      // Support array values
+      if (is_array($val)) {
+        $val = implode(',', $val);
+        $field->setValue($val);
+      }
+      if ($val) {
+        $data = $labels = array();
+        // Support serialized values
+        if (strpos($val, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) {
+          $val = str_replace(CRM_Core_DAO::VALUE_SEPARATOR, ',', trim($val, CRM_Core_DAO::VALUE_SEPARATOR));
+          $field->setValue($val);
+        }
+        foreach (explode(',', $val) as $v) {
+          $result = civicrm_api3($props['api']['entity'], $props['api']['action'], array('sequential' => 1, $props['api']['key'] => $v));
+          if (!empty($result['values'])) {
+            $data[] = array('id' => $v, 'text' => $result['values'][0][$props['api']['label']]);
+            $labels[] = $result['values'][0][$props['api']['label']];
+          }
+        }
+        if ($field->isFrozen()) {
+          $field->removeAttribute('class');
+          $field->setValue(implode(', ', $labels));
+        }
+        elseif ($data) {
+          if (empty($props['select']['multiple'])) {
+            $data = $data[0];
+          }
+          $field->setAttribute('data-entity-value', json_encode($data));
+        }
+      }
+    }
+  }
+
   /**
    * Convert all date fields within the params to mysql date ready for the
    * BAO layer. In this case fields are checked against the $_datefields defined for the form
@@ -1353,8 +1370,14 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
       // event form stores as an indexed array, contribution form not so much...
       $tempID = $this->_params[0]['select_contact_id'];
     }
+
     // force to ignore the authenticated user
-    if ($tempID === '0') {
+    if ($tempID === '0' || $tempID === 0) {
+      // we set the cid on the form so that this will be retained for the Confirm page
+      // in the multi-page form & prevent us returning the $userID when this is called
+      // from that page
+      // we don't really need to set it when $tempID is set because the params have that stored
+      $this->set('cid', 0);
       return $tempID;
     }