*/
require_once 'HTML/QuickForm/Page.php';
+
+/**
+ * Class CRM_Core_Form
+ */
class CRM_Core_Form extends HTML_QuickForm_Page {
/**
* If u have multiple groups of checkboxes, you will need to give them different
* ids to avoid potential name collision
*
- * @var const string / int
+ * @var string|int
*/
CONST CB_PREFIX = 'mark_x_', CB_PREFIY = 'mark_y_', CB_PREFIZ = 'mark_z_', CB_PREFIX_LEN = 7;
+ /**
+ * @internal to keep track of chain-select fields
+ * @var array
+ */
+ private $_chainSelectFields = array();
+
/**
* Constructor for the basic form page
*
* of default convenient functions, rules and buttons
*
* @param object $state State associated with this form
- * @param \const|\enum $action The mode the form is operating in (None/Create/View/Update/Delete)
+ * @param \const|\enum|int $action The mode the form is operating in (None/Create/View/Update/Delete)
* @param string $method The type of http method used (GET/POST)
* @param string $name The name of the form if different from class name
*
* @return \CRM_Core_Form
- @access public
+ * @access public
*/
function __construct(
$state = NULL,
$this->_name = $name;
}
else {
+ // CRM-15153 - FIXME this name translates to a DOM id and is not always unique!
$this->_name = CRM_Utils_String::getClassName(CRM_Utils_System::getClassName($this));
}
if (!isset(self::$_template)) {
self::$_template = CRM_Core_Smarty::singleton();
}
+ // Workaround for CRM-15153 - give each form a reasonably unique css class
+ $this->addClass(CRM_Utils_System::getClassName($this));
$this->assign('snippet', CRM_Utils_Array::value('snippet', $_GET));
}
static function generateID() {
}
+ /**
+ * Add one or more css classes to the form
+ * @param $className
+ */
+ public function addClass($className) {
+ $classes = $this->getAttribute('class');
+ $this->setAttribute('class', ($classes ? "$classes " : '') . $className);
+ }
+
/**
* register all the standard rules that most forms potentially use
*
* Simple easy to use wrapper around addElement. Deal with
* simple validation rules
*
- * @param $type
- * @param $name
+ * @param string $type
+ * @param string $name
* @param string $label
- * @param string $attributes
+ * @param string|array $attributes (options for select elements)
* @param bool $required
- * @param null $extra
- *
- * @internal param \type $string of html element to be added
- * @internal param \name $string of the html element
- * @internal param \display $string label for the html element
- * @internal param \attributes $string used for this element.
- * These are not default values
- * @internal param \is $bool this a required field
+ * @param array $extra (attributes for select elements)
*
* @return HTML_QuickForm_Element could be an error object
* @access public
function &add($type, $name, $label = '',
$attributes = '', $required = FALSE, $extra = NULL
) {
- // Normalize this property
- if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
- $extra['multiple'] = 'multiple';
+ if ($type == 'select' && is_array($extra)) {
+ // Normalize this property
+ if (!empty($extra['multiple'])) {
+ $extra['multiple'] = 'multiple';
+ }
+ else {
+ unset($extra['multiple']);
+ }
+ // Add placeholder option for select
+ if (isset($extra['placeholder'])) {
+ if ($extra['placeholder'] === TRUE) {
+ $extra['placeholder'] = $required ? ts('- select -') : ts('- none -');
+ }
+ if (($extra['placeholder'] || $extra['placeholder'] === '') && empty($extra['multiple']) && is_array($attributes) && !isset($attributes[''])) {
+ $attributes = array('' => $extra['placeholder']) + $attributes;
+ }
+ }
}
$element = $this->addElement($type, $name, $label, $attributes, $extra);
if (HTML_QuickForm::isError($element)) {
/**
* This function is just a wrapper, so that we can call all the hook functions
+ * @param bool $allowAjax - FIXME: This feels kind of hackish, ideally we would take the json-related code from this function
+ * and bury it deeper down in the controller
*/
- function mainProcess() {
+ function mainProcess($allowAjax = TRUE) {
$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))) {
+ if ($allowAjax && !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)) {
*/
function addRules() {}
+ /**
+ * Performs the server side validation
+ * @access public
+ * @since 1.0
+ * @return boolean true if no error found
+ * @throws HTML_QuickForm_Error
+ */
function validate() {
$error = parent::validate();
+ $this->validateChainSelectFields();
+
$hookErrors = CRM_Utils_Hook::validate(
get_class($this),
$this->_submitValues,
$js = CRM_Utils_Array::value('js', $button);
$isDefault = CRM_Utils_Array::value('isDefault', $button, FALSE);
if ($isDefault) {
- $attrs = array('class' => 'form-submit default');
+ $attrs = array('class' => 'crm-form-submit default');
}
else {
- $attrs = array('class' => 'form-submit');
+ $attrs = array('class' => 'crm-form-submit');
}
if ($js) {
* @access public
*/
function toSmarty() {
+ $this->preProcessChainSelectFields();
$renderer = $this->getRenderer();
$this->accept($renderer);
$content = $renderer->toArray();
$content['formName'] = $this->getName();
+ // CRM-15153
+ $content['formClass'] = CRM_Utils_System::getClassName($this);
return $content;
}
return self::$_template->get_template_vars($name);
}
+ /**
+ * @param $name
+ * @param $title
+ * @param $values
+ * @param array $attributes
+ * @param null $separator
+ * @param bool $required
+ *
+ * @return HTML_QuickForm_group
+ */
function &addRadio($name, $title, $values, $attributes = array(), $separator = NULL, $required = FALSE) {
$options = array();
$attributes = $attributes ? $attributes : array();
return $group;
}
+ /**
+ * @param $id
+ * @param $title
+ * @param bool $allowClear
+ * @param null $required
+ * @param array $attributes
+ */
function addYesNo($id, $title, $allowClear = FALSE, $required = NULL, $attributes = array()) {
$attributes += array('id_suffix' => $id);
$choice = array();
}
}
+ /**
+ * @param $id
+ * @param $title
+ * @param $values
+ * @param null $other
+ * @param null $attributes
+ * @param null $required
+ * @param null $javascriptMethod
+ * @param string $separator
+ * @param bool $flipValues
+ */
function addCheckBox($id, $title, $values, $other = NULL,
$attributes = NULL, $required = NULL,
$javascriptMethod = NULL,
$this->addButtons($buttons);
}
+ /**
+ * @param $name
+ * @param string $from
+ * @param string $to
+ * @param string $label
+ * @param string $dateFormat
+ * @param bool $required
+ * @param bool $displayTime
+ */
function addDateRange($name, $from = '_from', $to = '_to', $label = 'From:', $dateFormat = 'searchDate', $required = FALSE, $displayTime = FALSE) {
if ($displayTime) {
$this->addDateTime($name . $from, $label, $required, array('formatType' => $dateFormat));
if (!array_key_exists('placeholder', $props)) {
$props['placeholder'] = $required ? ts('- select -') : ts('- none -');
}
- if ($props['placeholder'] !== NULL && empty($props['multiple'])) {
- $options = array('' => '') + $options;
- }
// Handle custom field
if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) {
list(, $id) = explode('_', $name);
));
}
+ /**
+ * @param $name
+ * @param $label
+ * @param $attributes
+ * @param bool $forceTextarea
+ */
function addWysiwyg($name, $label, $attributes, $forceTextarea = FALSE) {
// 1. Get configuration option for editor (tinymce, ckeditor, pure textarea)
// 2. Based on the option, initialise proper editor
$this->assign('includeWysiwygEditor', $includeWysiwygEditor);
}
+ /**
+ * @param $id
+ * @param $title
+ * @param null $required
+ * @param null $extra
+ */
function addCountry($id, $title, $required = NULL, $extra = NULL) {
$this->addElement('select', $id, $title,
array(
}
}
+ /**
+ * @param $name
+ * @param $label
+ * @param $options
+ * @param $attributes
+ * @param null $required
+ * @param null $javascriptMethod
+ */
function addSelectOther($name, $label, $options, $attributes, $required = NULL, $javascriptMethod = NULL) {
$this->addElement('select', $name . '_id', $label, $options, $javascriptMethod);
}
}
+ /**
+ * @return null
+ */
public function getRootTitle() {
return NULL;
}
+ /**
+ * @return string
+ */
public function getCompleteTitle() {
return $this->getRootTitle() . $this->getTitle();
}
+ /**
+ * @return CRM_Core_Smarty
+ */
static function &getTemplate() {
return self::$_template;
}
+ /**
+ * @param $elementName
+ */
function addUploadElement($elementName) {
$uploadNames = $this->get('uploadNames');
if (!$uploadNames) {
}
}
+ /**
+ * @return string
+ */
function buttonType() {
$uploadNames = $this->get('uploadNames');
$buttonType = (is_array($uploadNames) && !empty($uploadNames)) ? 'upload' : 'next';
return $buttonType;
}
+ /**
+ * @param $name
+ *
+ * @return null
+ */
function getVar($name) {
return isset($this->$name) ? $this->$name : NULL;
}
+ /**
+ * @param $name
+ * @param $value
+ */
function setVar($name, $value) {
$this->$name = $value;
}
}
}
+ /**
+ * @param $elementName
+ */
function removeFileRequiredRules($elementName) {
$this->_required = array_diff($this->_required, array($elementName));
if (isset($this->_rules[$elementName])) {
function allowAjaxSubmit() {
$this->removeAttribute('data-no-ajax-submit');
}
+
+ /**
+ * Sets page title based on entity and action
+ * @param string $entityLabel
+ */
+ function setPageTitle($entityLabel) {
+ switch ($this->_action) {
+ case CRM_Core_Action::ADD:
+ CRM_Utils_System::setTitle(ts('New %1', array(1 => $entityLabel)));
+ break;
+ case CRM_Core_Action::UPDATE:
+ CRM_Utils_System::setTitle(ts('Edit %1', array(1 => $entityLabel)));
+ break;
+ case CRM_Core_Action::VIEW:
+ case CRM_Core_Action::PREVIEW:
+ CRM_Utils_System::setTitle(ts('View %1', array(1 => $entityLabel)));
+ break;
+ case CRM_Core_Action::DELETE:
+ CRM_Utils_System::setTitle(ts('Delete %1', array(1 => $entityLabel)));
+ break;
+ }
+ }
+
+ /**
+ * Create a chain-select target field. All settings are optional; the defaults usually work.
+ *
+ * @param string $elementName
+ * @param array $settings
+ *
+ * @return HTML_QuickForm_Element
+ */
+ public function addChainSelect($elementName, $settings = array()) {
+ $props = $settings += array(
+ 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
+ 'data-callback' => strpos($elementName, 'rovince') ? 'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
+ 'label' => strpos($elementName, 'rovince') ? ts('State / Province') : ts('County'),
+ 'data-empty-prompt' => strpos($elementName, 'rovince') ? ts('Choose country first') : ts('Choose state first'),
+ 'data-none-prompt' => ts('- N/A -'),
+ 'multiple' => FALSE,
+ 'required' => FALSE,
+ 'placeholder' => empty($settings['required']) ? ts('- none -') : ts('- select -'),
+ );
+ CRM_Utils_Array::remove($props, 'label', 'required', 'control_field');
+ $props['class'] = (empty($props['class']) ? '' : "{$props['class']} ") . 'crm-chain-select-target crm-select2';
+ $props['data-select-prompt'] = $props['placeholder'];
+ $props['data-name'] = $elementName;
+
+ $this->_chainSelectFields[$settings['control_field']] = $elementName;
+
+ return $this->add('select', $elementName, $settings['label'], array(), $settings['required'], $props);
+ }
+
+ /**
+ * Set options and attributes for chain select fields based on the controlling field's value
+ */
+ private function preProcessChainSelectFields() {
+ foreach ($this->_chainSelectFields as $control => $target) {
+ $controlField = $this->getElement($control);
+ $targetField = $this->getElement($target);
+ $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'stateProvince' : 'country';
+
+ $css = (string) $controlField->getAttribute('class');
+ $controlField->updateAttributes(array(
+ 'class' => ($css ? "$css " : 'crm-select2 ') . 'crm-chain-select-control',
+ 'data-target' => $target,
+ ));
+ $controlValue = $controlField->getValue();
+ $options = array();
+ if ($controlValue) {
+ $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
+ if (!$options) {
+ $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
+ }
+ }
+ else {
+ $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
+ $targetField->setAttribute('disabled', 'disabled');
+ }
+ if (!$targetField->getAttribute('multiple')) {
+ $options = array('' => $targetField->getAttribute('placeholder')) + $options;
+ $targetField->removeAttribute('placeholder');
+ }
+ $targetField->loadArray($options);
+ }
+ }
+
+ /**
+ * Validate country / state / county match and suppress unwanted "required" errors
+ */
+ private function validateChainSelectFields() {
+ foreach ($this->_chainSelectFields as $control => $target) {
+ $controlValue = (array) $this->getElementValue($control);
+ $targetField = $this->getElement($target);
+ $controlType = $targetField->getAttribute('data-callback') == 'civicrm/ajax/jqCounty' ? 'stateProvince' : 'country';
+ $targetValue = array_filter((array) $targetField->getValue());
+ if ($targetValue || $this->getElementError($target)) {
+ $options = CRM_Core_BAO_Location::getChainSelectValues($controlValue, $controlType, TRUE);
+ if ($targetValue) {
+ if (!array_intersect($targetValue, array_keys($options))) {
+ $this->setElementError($target, $controlType == 'country' ? ts('State/Province does not match the selected Country') : ts('County does not match the selected State/Province'));
+ }
+ }
+ // Suppress "required" error for field if it has no options
+ elseif (!$options) {
+ $this->setElementError($target, NULL);
+ }
+ }
+ }
+ }
}