<?php
/*
+--------------------------------------------------------------------+
- | CiviCRM version 4.4 |
+ | CiviCRM version 4.5 |
+--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2013 |
+ | Copyright CiviCRM LLC (c) 2004-2014 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
* machine. Each form can also operate in various modes
*
* @package CRM
- * @copyright CiviCRM LLC (c) 2004-2013
+ * @copyright CiviCRM LLC (c) 2004-2014
* $Id$
*
*/
require_once 'HTML/QuickForm/Page.php';
+
+/**
+ * Class CRM_Core_Form
+ */
class CRM_Core_Form extends HTML_QuickForm_Page {
/**
*/
static protected $_template;
+ /**
+ * Indicate if this form should warn users of unsaved changes
+ */
+ protected $unsavedChangesWarn;
+
/**
* What to return to the client if in ajax mode (snippet=json)
*
*/
public $urlPath = array();
+ /**
+ * @var CRM_Core_Controller
+ */
+ public $controller;
+
/**
* constants for attributes for various form elements
* attempt to standardize on the number of variations that we
* 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;
* We should not use QuickForm directly. This class provides a lot
* of default convenient functions, rules and buttons
*
- * @param object $state State associated with this form
- * @param enum $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
+ * @param object $state State associated with this form
+ * @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 object
+ * @return \CRM_Core_Form
* @access public
*/
function __construct(
self::$_template = CRM_Core_Smarty::singleton();
}
- $this->assign('snippet', (int) CRM_Utils_Array::value('snippet', $_REQUEST));
+ $this->assign('snippet', CRM_Utils_Array::value('snippet', $_GET));
}
static function generateID() {
* Simple easy to use wrapper around addElement. Deal with
* simple validation rules
*
- * @param string type of html element to be added
- * @param string name of the html element
- * @param string display label for the html element
- * @param string attributes used for this element.
+ * @param $type
+ * @param $name
+ * @param string $label
+ * @param string $attributes
+ * @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
- * @param bool is this a required field
+ * @internal param \is $bool this a required field
*
* @return HTML_QuickForm_Element could be an error object
* @access public
- *
*/
function &add($type, $name, $label = '',
- $attributes = '', $required = FALSE, $javascript = NULL
+ $attributes = '', $required = FALSE, $extra = NULL
) {
- $element = $this->addElement($type, $name, $label, $attributes, $javascript);
+ // Normalize this property
+ if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
+ $extra['multiple'] = 'multiple';
+ }
+ $element = $this->addElement($type, $name, $label, $attributes, $extra);
if (HTML_QuickForm::isError($element)) {
CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($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();
CRM_Utils_Hook::buildForm(get_class($this), $this);
$this->addRules();
+
+ //Set html data-attribute to enable warning user of unsaved changes
+ if ($this->unsavedChangesWarn === true
+ || (!isset($this->unsavedChangesWarn)
+ && ($this->_action & CRM_Core_Action::ADD || $this->_action & CRM_Core_Action::UPDATE)
+ )
+ ) {
+ $this->setAttribute('data-warn-changes', 'true');
+ }
}
/**
$attrs = array_merge($js, $attrs);
}
+ if ($button['type'] === 'cancel') {
+ $attrs['class'] .= ' cancel';
+ }
+
if ($button['type'] === 'reset') {
$prevnext[] = $this->createElement($button['type'], 'reset', $button['name'], $attrs);
}
$buttonName = $this->getButtonName($button['type']);
}
- if (in_array($button['type'], array(
- 'next', 'upload')) && $button['name'] === 'Save') {
+ if (in_array($button['type'], array('next', 'upload', 'done')) && $button['name'] === ts('Save')) {
$attrs = array_merge($attrs, (array('accesskey' => 'S')));
}
$prevnext[] = $this->createElement('submit', $buttonName, $button['name'], $attrs);
/**
* assign value to name in template
*
- * @param array|string $name name of variable
+ * @param $var
* @param mixed $value value of varaible
*
+ * @internal param array|string $name name of variable
* @return void
* @access public
*/
/**
* assign value to name in template by reference
*
- * @param array|string $name name of variable
+ * @param $var
* @param mixed $value value of varaible
*
+ * @internal param array|string $name name of variable
* @return void
* @access public
*/
* Returns an array containing template variables
*
* @param string $name
- * @param string $type
+ *
+ * @internal param string $type
* @return array
*/
function get_template_vars($name=null) {
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();
- $unselectable = !empty($attributes['unselectable']);
- unset($attributes['unselectable']);
+ $allowClear = !empty($attributes['allowClear']);
+ unset($attributes['allowClear']);
$attributes += array('id_suffix' => $name);
foreach ($values as $key => $var) {
$options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
if ($required) {
$this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
}
- if ($unselectable) {
- $group->setAttribute('unselectable', TRUE);
+ if ($allowClear) {
+ $group->setAttribute('allowClear', TRUE);
}
return $group;
}
- function addYesNo($id, $title, $unselectable = FALSE, $required = NULL, $attributes = array()) {
+ /**
+ * @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();
$choice[] = $this->createElement('radio', NULL, '11', ts('Yes'), '1', $attributes);
$choice[] = $this->createElement('radio', NULL, '11', ts('No'), '0', $attributes);
$group = $this->addGroup($choice, $id, $title);
- if ($unselectable) {
- $group->setAttribute('unselectable', TRUE);
+ if ($allowClear) {
+ $group->setAttribute('allowClear', TRUE);
}
if ($required) {
$this->addRule($id, ts('%1 is a required field.', array(1 => $title)), 'required');
}
}
+ /**
+ * @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,
* the form with a customized title for the main Submit
*
* @param string $title title of the main button
- * @param string $type button type for the form after processing
- * @param string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
+ * @param string $nextType
+ * @param string $backType
+ * @param bool|string $submitOnce If true, add javascript to next button submit which prevents it from being clicked more than once
*
+ * @internal param string $type button type for the form after processing
* @return void
* @access public
*/
$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));
* - 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
* @param bool $required
* @throws CRM_Core_Exception
* @return HTML_QuickForm_Element
));
}
+ /**
+ * @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;
}
* - entity - defaults to contact
* - create - can the user create a new entity on-the-fly?
* Set to TRUE if entity is contact and you want the default profiles,
- * or pass in your own set of links. See output of CRM_Core_BAO_UFGroup::getCreateLinks for format
- * - api - array of settings for the getlist api
+ * or pass in your own set of links. @see CRM_Core_BAO_UFGroup::getCreateLinks for format
+ * note that permissions are checked automatically
+ * - api - array of settings for the getlist api wrapper
+ * note that it accepts a 'params' setting which will be passed to the underlying api
* - placeholder - string
* - multiple - bool
* - class, etc. - other html properties
* @access public
* @return HTML_QuickForm_Element
*/
- function addEntityRef($name, $label, $props = array(), $required = FALSE) {
+ function addEntityRef($name, $label = '', $props = array(), $required = FALSE) {
+ require_once "api/api.php";
$config = CRM_Core_Config::singleton();
// Default properties
$props['api'] = CRM_Utils_Array::value('api', $props, array());
- $props['entity'] = CRM_Utils_Array::value('entity', $props, 'contact');
-
- $props['class'] = isset($props['class']) ? $props['class'] . ' ' : '';
- $props['class'] .= "crm-select2 crm-form-entityref";
+ $props['entity'] = _civicrm_api_get_entity_name_from_camel(CRM_Utils_Array::value('entity', $props, 'contact'));
+ $props['class'] = ltrim(CRM_Utils_Array::value('class', $props, '') . ' crm-form-entityref');
if ($props['entity'] == 'contact' && isset($props['create']) && !(CRM_Core_Permission::check('edit all contacts') || CRM_Core_Permission::check('add contacts'))) {
unset($props['create']);
}
- if ($props['entity'] == 'contact' && isset($props['create']) && $props['create'] === TRUE) {
- if (empty($props['api']['params']['contact_type'])) {
- $props['create'] = CRM_Core_BAO_UFGroup::getCreateLinks(array('new_individual', 'new_organization', 'new_household'));
- }
- else {
- $props['create'] = CRM_Core_BAO_UFGroup::getCreateLinks('new_' . strtolower($props['api']['params']['contact_type']));
- }
- }
- $defaults = array(
- 'minimumInputLength' => 1,
- 'multiple' => !empty($props['multiple']),
- 'placeholder' => CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select -') : ts('- none -')),
- 'allowClear' => !$required,
- );
- if ($props['entity'] == 'contact') {
- $defaults['formatInputTooShort'] = $config->includeEmailInName ? ts('Start typing a name or email...') : ts('Start typing a name...');
+ $props['placeholder'] = CRM_Utils_Array::value('placeholder', $props, $required ? ts('- select %1 -', array(1 => ts(str_replace('_', ' ', $props['entity'])))) : ts('- none -'));
+
+ $defaults = array();
+ if (!empty($props['multiple'])) {
+ $defaults['multiple'] = TRUE;
}
$props['select'] = CRM_Utils_Array::value('select', $props, array()) + $defaults;
if (!empty($props['create'])) {
$props['data-create-links'] = json_encode($props['create']);
}
- CRM_Utils_Array::remove($props, 'multiple', 'select', 'api', 'entity', 'placeholder', 'create');
+ CRM_Utils_Array::remove($props, 'multiple', 'select', 'api', 'entity', 'create');
}
/**
}
}
+ /**
+ * @param $elementName
+ */
function removeFileRequiredRules($elementName) {
$this->_required = array_diff($this->_required, array($elementName));
if (isset($this->_rules[$elementName])) {
}
}
-/**
- * Get contact if for a form object. Prioritise
- * - cid in URL if 0 (on behalf on someoneelse)
- * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
- * - logged in user id if it matches the one in the cid in the URL
- * - contact id validated from a checksum from a checksum
- * - cid from the url if the caller has ACL permission to view
- * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
- *
- * @return Ambigous <mixed, NULL, value, unknown, array, number>|unknown
- */
+ /**
+ * Get contact if for a form object. Prioritise
+ * - cid in URL if 0 (on behalf on someoneelse)
+ * (@todo consider setting a variable if onbehalf for clarity of downstream 'if's
+ * - logged in user id if it matches the one in the cid in the URL
+ * - contact id validated from a checksum from a checksum
+ * - cid from the url if the caller has ACL permission to view
+ * - fallback is logged in user (or ? NULL if no logged in user) (@todo wouldn't 0 be more intuitive?)
+ *
+ * @return mixed NULL|integer
+ */
function getContactID() {
$tempID = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
if(isset($this->_params) && isset($this->_params['select_contact_id'])) {
* </div>
* </div>
* {/if}
+ *
* @param array $profiles ids of profiles that are on the form (to be autofilled)
- * @param array $field metadata of field to use as selector including
+ * @param array $autoCompleteField
+ *
+ * @internal param array $field metadata of field to use as selector including
* - name_field
* - id_field
* - url (for ajax lookup)
*
- * @todo add data attributes so we can deal with multiple instances on a form
+ * @todo add data attributes so we can deal with multiple instances on a form
*/
function addAutoSelector($profiles = array(), $autoCompleteField = array()) {
$autoCompleteField = array_merge(array(
- 'name_field' => 'select_contact',
'id_field' => 'select_contact_id',
- 'field_text' => ts('Select Contact'),
+ 'placeholder' => ts('Select someone else ...'),
'show_hide' => TRUE,
- 'show_text' => ts('to select someone already in our database.'),
- 'hide_text' => ts('to clear this person\'s information, and fill the form in for someone else'),
- 'url' => array('civicrm/ajax/rest', 'className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1'),
- 'max' => civicrm_api3('setting', 'getvalue', array(
- 'name' => 'search_autocomplete_count',
- 'group' => 'Search Preferences',
- ))
+ 'api' => array('params' => array('contact_type' => 'Individual'))
), $autoCompleteField);
- if(0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1)))) {
- $this->addElement('text', $autoCompleteField['name_field'] , $autoCompleteField['field_text']);
- $this->addElement('hidden', $autoCompleteField['id_field'], '', array('id' => $autoCompleteField['id_field']));
+ if($this->canUseAjaxContactLookups()) {
$this->assign('selectable', $autoCompleteField['id_field']);
+ $this->addEntityRef($autoCompleteField['id_field'], NULL, array('placeholder' => $autoCompleteField['placeholder'], 'api' => $autoCompleteField['api']));
- CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AutoComplete.js')
+ CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'js/AlternateContactSelector.js')
->addSetting(array(
'form' => array('autocompletes' => $autoCompleteField),
'ids' => array('profile' => $profiles),
}
}
+ /**
+ *
+ */
+ function canUseAjaxContactLookups() {
+ if (0 < (civicrm_api3('contact', 'getcount', array('check_permissions' => 1))) &&
+ CRM_Core_Permission::check(array(array('access AJAX API', 'access CiviCRM')))) {
+ return TRUE;
+ }
+ }
+
/**
* Add the options appropriate to cid = zero - ie. autocomplete
*
/**
* Set default values on form for given contact (or no contact defaults)
+ *
* @param mixed $profile_id (can be id, or profile name)
* @param integer $contactID
+ *
+ * @return array
*/
function getProfileDefaults($profile_id = 'Billing', $contactID = NULL) {
try{
return array();
}
}
+
+ /**
+ * Sets form attribute
+ * @see CRM.loadForm
+ */
+ function preventAjaxSubmit() {
+ $this->setAttribute('data-no-ajax-submit', 'true');
+ }
+
+ /**
+ * Sets form attribute
+ * @see CRM.loadForm
+ */
+ function allowAjaxSubmit() {
+ $this->removeAttribute('data-no-ajax-submit');
+ }
}