/** * Adds a field based on metadata. * * @param $name * Field name to go on the form. * @param array $props * Mix of html attributes and special properties, namely. * - entity (api entity name, can usually be inferred automatically from the form class) * - name (field name - only needed if different from name used on the form) * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link * - placeholder - set to NULL to disable * - multiple - bool * - context - @see CRM_Core_DAO::buildOptionsContext * @param bool $required * @throws \CiviCRM_API3_Exception * @throws \Exception * @return HTML_QuickForm_Element */ public function addField($name, $props = array(), $required = FALSE) { // Resolve context. if (empty($props['context'])) { $props['context'] = $this->getDefaultContext(); } $context = $props['context']; // Resolve entity. if (empty($props['entity'])) { $props['entity'] = $this->getDefaultEntity(); } // Resolve field. if (empty($props['name'])) { $props['name'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name; } // Resolve action. if (empty($props['action'])) { $props['action'] = $this->getApiAction(); } // Handle custom fields if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) { $fieldId = (int) substr($name, 7); return CRM_Core_BAO_CustomField::addQuickFormElement($this, $name, $fieldId, $required, $context == 'search', CRM_Utils_Array::value('label', $props)); } // Core field - get metadata. $fieldSpec = civicrm_api3($props['entity'], 'getfield', $props); $fieldSpec = $fieldSpec['values']; $label = CRM_Utils_Array::value('label', $props, isset($fieldSpec['title']) ? $fieldSpec['title'] : NULL); $widget = isset($props['type']) ? $props['type'] : $fieldSpec['html']['type']; if ($widget == 'TextArea' && $context == 'search') { $widget = 'Text'; } $isSelect = in_array($widget, array('Select', 'CheckBoxGroup', 'RadioGroup', 'Radio')); if ($isSelect) { // Fetch options from the api unless passed explicitly. if (isset($props['options'])) { $options = $props['options']; } else { $options = isset($fieldSpec['options']) ? $fieldSpec['options'] : NULL; } if ($context == 'search') { $widget = 'Select'; $props['multiple'] = CRM_Utils_Array::value('multiple', $props, TRUE); } // Add data for popup link. if ((!empty($props['option_url']) || !array_key_exists('option_url', $props)) && ($context != 'search' && $widget == 'Select' && CRM_Core_Permission::check('administer CiviCRM'))) { $props['data-option-edit-path'] = !empty($props['option_url']) ? $props['option_url'] : CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec); $props['data-api-entity'] = $props['entity']; $props['data-api-field'] = $props['name']; } } $props += CRM_Utils_Array::value('html', $fieldSpec, array()); CRM_Utils_Array::remove($props, 'entity', 'name', 'context', 'label', 'action', 'type', 'option_url', 'options'); // TODO: refactor switch statement, to separate methods. switch ($widget) { case 'Text': case 'Url': case 'Number': case 'Email': //TODO: Autodetect ranges $props['size'] = isset($props['size']) ? $props['size'] : 60; return $this->add(strtolower($widget), $name, $label, $props, $required); case 'hidden': return $this->add('hidden', $name, NULL, $props, $required); case 'TextArea': //Set default columns and rows for textarea. $props['rows'] = isset($props['rows']) ? $props['rows'] : 4; $props['cols'] = isset($props['cols']) ? $props['cols'] : 60; return $this->add('textarea', $name, $label, $props, $required); case 'Select Date': //TODO: add range support //TODO: Add date formats //TODO: Add javascript template for dates. return $this->addDate($name, $label, $required, $props); case 'Radio': $separator = isset($props['separator']) ? $props['separator'] : NULL; unset($props['separator']); if (!isset($props['allowClear'])) { $props['allowClear'] = !$required; } return $this->addRadio($name, $label, $options, $props, $separator, $required); case 'ChainSelect': $props += array('required' => $required, 'label' => $label, 'multiple' => $context == 'search'); return $this->addChainSelect($name, $props); case 'Select': $props['class'] = CRM_Utils_Array::value('class', $props, 'big') . ' crm-select2'; if (!array_key_exists('placeholder', $props)) { $props['placeholder'] = $required ? ts('- select -') : ($context == 'search' ? ts('- any -') : ts('- none -')); } // TODO: Add and/or option for fields that store multiple values return $this->add('select', $name, $label, $options, $required, $props); case 'CheckBoxGroup': return $this->addCheckBox($name, $label, array_flip($options), $required, $props); case 'RadioGroup': return $this->addRadio($name, $label, $options, $props, NULL, $required); case 'CheckBox': $text = isset($props['text']) ? $props['text'] : NULL; unset($props['text']); return $this->addElement('checkbox', $name, $label, $text, $props); //add support for 'Advcheckbox' field //add support for 'Advcheckbox' field case 'advcheckbox': $text = isset($props['text']) ? $props['text'] : NULL; unset($props['text']); return $this->addElement('advcheckbox', $name, $label, $text, $props); case 'File': // We should not build upload file in search mode. if ($context == 'search') { return; } $file = $this->add('file', $name, $label, $props, $required); $this->addUploadElement($name); return $file; case 'RichTextEditor': return $this->add('wysiwyg', $name, $label, $props, $required); case 'EntityRef': return $this->addEntityRef($name, $label, $props, $required); // Check datatypes of fields // case 'Int': //case 'Float': //case 'Money': //case read only fields // Check datatypes of fields // case 'Int': //case 'Float': //case 'Money': //case read only fields default: throw new Exception("Unsupported html-element " . $widget); } }
/** * Adds a field based on metadata. * * @param $name * Field name to go on the form. * @param array $props * Mix of html attributes and special properties, namely. * - entity (api entity name, can usually be inferred automatically from the form class) * - name (field name - only needed if different from name used on the form) * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link * - placeholder - set to NULL to disable * - multiple - bool * - context - @see CRM_Core_DAO::buildOptionsContext * @param bool $required * @throws \CiviCRM_API3_Exception * @throws \Exception * @return HTML_QuickForm_Element */ public function addField($name, $props = array(), $required = FALSE) { // TODO: Handle custom field if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) { throw new Exception("Custom fields are not supported by the addField method. "); } // Resolve context. if (!isset($props['context'])) { $props['context'] = $this->getDefaultContext(); } // Resolve entity. if (!isset($props['entity'])) { $props['entity'] = $this->getDefaultEntity(); } // Resolve field. if (!isset($props['name'])) { $props['name'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name; } // Resolve action. if (!isset($props['action'])) { $props['action'] = $this->getApiAction(); } // Get field metadata. $fieldSpec = civicrm_api3($props['entity'], 'getfield', $props); $fieldSpec = $fieldSpec['values']; $label = CRM_Utils_Array::value('label', $props, isset($fieldSpec['title']) ? $fieldSpec['title'] : NULL); $widget = isset($props['type']) ? $props['type'] : $fieldSpec['html']['type']; if ($widget == 'TextArea' && $props['context'] == 'search') { $widget = 'Text'; } $isSelect = in_array($widget, array('Select', 'Multi-Select', 'Select State/Province', 'Multi-Select State/Province', 'Select Country', 'Multi-Select Country', 'AdvMulti-Select', 'CheckBoxGroup', 'RadioGroup', 'Radio')); if ($isSelect) { // Fetch options from the api unless passed explicitly. if (isset($props['options'])) { $options = $props['options']; // Else this get passed to the form->add method. unset($props['options']); } else { $options = isset($fieldSpec['options']) ? $fieldSpec['options'] : NULL; } //@TODO AdvMulti-Select is deprecated, drop support. if ($props['context'] == 'search' || $widget !== 'AdvMulti-Select' && strpos($widget, 'Select') !== FALSE) { $widget = 'Select'; } // Set default options-url value. if (!isset($props['options-url'])) { $props['options-url'] = TRUE; } // Add data for popup link. if (isset($props['options-url']) && $props['options-url'] && ($props['context'] != 'search' && $widget == 'Select' && CRM_Core_Permission::check('administer CiviCRM'))) { $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : ($props['data-option-edit-path'] = CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec)); $props['data-api-entity'] = $props['entity']; $props['data-api-field'] = $props['name']; if (isset($props['options-url'])) { unset($props['options-url']); } } } //Use select2 library for following widgets. $isSelect2 = in_array($widget, array('Select', 'Multi-Select', 'Select State/Province', 'Multi-Select State/Province', 'Select Country', 'Multi-Select Country')); if ($isSelect2) { $props['class'] = (isset($props['class']) ? $props['class'] . ' ' : '') . "crm-select2"; if ($props['context'] == 'search' || strpos($widget, 'Multi') !== FALSE) { $props['class'] .= ' huge'; $props['multiple'] = 'multiple'; } // The placeholder is only used for select-elements. if (!array_key_exists('placeholder', $props)) { $props['placeholder'] = $required ? ts('- select -') : $props['context'] == 'search' ? ts('- any -') : ts('- none -'); } } $props += CRM_Utils_Array::value('html', $fieldSpec, array()); CRM_Utils_Array::remove($props, 'entity', 'name', 'context', 'label', 'action', 'type'); // TODO: refactor switch statement, to separate methods. switch ($widget) { case 'Text': case 'Link': //TODO: Autodetect ranges $props['size'] = isset($props['size']) ? $props['size'] : 60; return $this->add('text', $name, $label, $props, $required); case 'hidden': return $this->add('hidden', $name, NULL, $props, $required); case 'TextArea': //Set default columns and rows for textarea. $props['rows'] = isset($props['rows']) ? $props['rows'] : 4; $props['cols'] = isset($props['cols']) ? $props['cols'] : 60; return $this->addElement('textarea', $name, $label, $props, $required); case 'Select Date': //TODO: add range support //TODO: Add date formats //TODO: Add javascript template for dates. return $this->addDate($name, $label, $required, $props); case 'Radio': $separator = isset($props['separator']) ? $props['separator'] : NULL; unset($props['separator']); if (!isset($props['allowClear'])) { $props['allowClear'] = !$required; } return $this->addRadio($name, $label, $options, $props, $separator, $required); case 'Select': if (empty($props['multiple'])) { $options = array('' => $props['placeholder']) + $options; } if (!empty($props['data-api-field']) && in_array($props['data-api-field'], array('state_province_id', 'county_id'))) { return $this->addChainSelect($name, $props); } // TODO: Add and/or option for fields that store multiple values return $this->add('select', $name, $label, $options, $required, $props); case 'CheckBoxGroup': return $this->addCheckBox($name, $label, array_flip($options), $required, $props); case 'RadioGroup': return $this->addRadio($name, $label, $options, $props, NULL, $required); //case 'AdvMulti-Select': //case 'AdvMulti-Select': case 'CheckBox': $text = isset($props['text']) ? $props['text'] : NULL; unset($props['text']); return $this->addElement('checkbox', $name, $label, $text, $props); case 'File': // We should not build upload file in search mode. if (isset($props['context']) && $props['context'] == 'search') { return; } $file = $this->add('file', $name, $label, $props, $required); $this->addUploadElement($name); return $file; //case 'RichTextEditor': //TODO: Add javascript template for wysiwyg. //case 'RichTextEditor': //TODO: Add javascript template for wysiwyg. case 'Autocomplete-Select': case 'EntityRef': return $this->addEntityRef($name, $label, $props, $required); // Check datatypes of fields // case 'Int': //case 'Float': //case 'Money': //case 'Link': //case read only fields // Check datatypes of fields // case 'Int': //case 'Float': //case 'Money': //case 'Link': //case read only fields default: throw new Exception("Unsupported html-element " . $widget); } }
/** * Adds a select based on field metadata. * TODO: This could be even more generic and widget type (select in this case) could also be read from metadata * Perhaps a method like $form->bind($name) which would look up all metadata for named field * @param $name * Field name to go on the form. * @param array $props * Mix of html attributes and special properties, namely. * - entity (api entity name, can usually be inferred automatically from the form class) * - field (field name - only needed if different from name used on the form) * - option_url - path to edit this option list - usually retrieved automatically - set to NULL to disable link * - placeholder - set to NULL to disable * - multiple - bool * - context - @see CRM_Core_DAO::buildOptionsContext * @param bool $required * @throws CRM_Core_Exception * @return HTML_QuickForm_Element */ public function addSelect($name, $props = array(), $required = FALSE) { if (!isset($props['entity'])) { $props['entity'] = CRM_Utils_Api::getEntityName($this); } if (!isset($props['field'])) { $props['field'] = strrpos($name, '[') ? rtrim(substr($name, 1 + strrpos($name, '[')), ']') : $name; } // Fetch options from the api unless passed explicitly if (isset($props['options'])) { $options = $props['options']; } else { $info = civicrm_api3($props['entity'], 'getoptions', $props); $options = $info['values']; } if (!array_key_exists('placeholder', $props)) { $props['placeholder'] = $required ? ts('- select -') : CRM_Utils_Array::value('context', $props) == 'search' ? ts('- any -') : ts('- none -'); } // Handle custom field if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) { list(, $id) = explode('_', $name); $label = isset($props['label']) ? $props['label'] : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'label', $id); $gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', 'option_group_id', $id); if (CRM_Utils_Array::value('context', $props) != 'search') { $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : 'civicrm/admin/options/' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $gid); } } else { $info = civicrm_api3($props['entity'], 'getfields'); foreach ($info['values'] as $uniqueName => $fieldSpec) { if ($uniqueName === $props['field'] || CRM_Utils_Array::value('name', $fieldSpec) === $props['field'] || in_array($props['field'], CRM_Utils_Array::value('api.aliases', $fieldSpec, array()))) { break; } } $label = isset($props['label']) ? $props['label'] : $fieldSpec['title']; if (CRM_Utils_Array::value('context', $props) != 'search') { $props['data-option-edit-path'] = array_key_exists('option_url', $props) ? $props['option_url'] : ($props['data-option-edit-path'] = CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec)); } } $props['class'] = (isset($props['class']) ? $props['class'] . ' ' : '') . "crm-select2"; $props['data-api-entity'] = $props['entity']; $props['data-api-field'] = $props['field']; CRM_Utils_Array::remove($props, 'label', 'entity', 'field', 'option_url', 'options', 'context'); return $this->add('select', $name, $label, $options, $required, $props); }