| 1 | <?php |
| 2 | /* |
| 3 | +--------------------------------------------------------------------+ |
| 4 | | CiviCRM version 4.7 | |
| 5 | +--------------------------------------------------------------------+ |
| 6 | | Copyright CiviCRM LLC (c) 2004-2015 | |
| 7 | +--------------------------------------------------------------------+ |
| 8 | | This file is a part of CiviCRM. | |
| 9 | | | |
| 10 | | CiviCRM is free software; you can copy, modify, and distribute it | |
| 11 | | under the terms of the GNU Affero General Public License | |
| 12 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | |
| 13 | | | |
| 14 | | CiviCRM is distributed in the hope that it will be useful, but | |
| 15 | | WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
| 17 | | See the GNU Affero General Public License for more details. | |
| 18 | | | |
| 19 | | You should have received a copy of the GNU Affero General Public | |
| 20 | | License and the CiviCRM Licensing Exception along | |
| 21 | | with this program; if not, contact CiviCRM LLC | |
| 22 | | at info[AT]civicrm[DOT]org. If you have questions about the | |
| 23 | | GNU Affero General Public License or the licensing of CiviCRM, | |
| 24 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | |
| 25 | +--------------------------------------------------------------------+ |
| 26 | */ |
| 27 | |
| 28 | /** |
| 29 | * |
| 30 | * @package CRM |
| 31 | * @copyright CiviCRM LLC (c) 2004-2015 |
| 32 | * $Id$ |
| 33 | * |
| 34 | */ |
| 35 | |
| 36 | require_once 'HTML/QuickForm/Renderer/ArraySmarty.php'; |
| 37 | |
| 38 | /** |
| 39 | * Customize QF output to meet our specific requirements |
| 40 | */ |
| 41 | class CRM_Core_Form_Renderer extends HTML_QuickForm_Renderer_ArraySmarty { |
| 42 | |
| 43 | /** |
| 44 | * We only need one instance of this object. So we use the singleton |
| 45 | * pattern and cache the instance in this variable |
| 46 | * |
| 47 | * @var object |
| 48 | */ |
| 49 | static private $_singleton = NULL; |
| 50 | |
| 51 | /** |
| 52 | * The converter from array size to css class. |
| 53 | * |
| 54 | * @var array |
| 55 | */ |
| 56 | static $_sizeMapper = array( |
| 57 | 2 => 'two', |
| 58 | 4 => 'four', |
| 59 | 6 => 'six', |
| 60 | 8 => 'eight', |
| 61 | 12 => 'twelve', |
| 62 | 20 => 'medium', |
| 63 | 30 => 'big', |
| 64 | 45 => 'huge', |
| 65 | ); |
| 66 | |
| 67 | /** |
| 68 | * Constructor. |
| 69 | */ |
| 70 | public function __construct() { |
| 71 | $template = CRM_Core_Smarty::singleton(); |
| 72 | parent::__construct($template); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Static instance provider. |
| 77 | * |
| 78 | * Method providing static instance of as in Singleton pattern. |
| 79 | */ |
| 80 | public static function &singleton() { |
| 81 | if (!isset(self::$_singleton)) { |
| 82 | self::$_singleton = new CRM_Core_Form_Renderer(); |
| 83 | } |
| 84 | return self::$_singleton; |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Creates an array representing an element containing. |
| 89 | * the key for storing this. We allow the parent to do most of the |
| 90 | * work, but then we add some CiviCRM specific enhancements to |
| 91 | * make the html compliant with our css etc |
| 92 | * |
| 93 | * |
| 94 | * @param HTML_QuickForm_element $element |
| 95 | * @param bool $required |
| 96 | * Whether an element is required. |
| 97 | * @param string $error |
| 98 | * Error associated with the element. |
| 99 | * |
| 100 | * @return array |
| 101 | */ |
| 102 | public function _elementToArray(&$element, $required, $error) { |
| 103 | self::updateAttributes($element, $required, $error); |
| 104 | |
| 105 | $el = parent::_elementToArray($element, $required, $error); |
| 106 | |
| 107 | // add label html |
| 108 | if (!empty($el['label'])) { |
| 109 | $id = $element->getAttribute('id'); |
| 110 | if (!empty($id)) { |
| 111 | $el['label'] = '<label for="' . $id . '">' . $el['label'] . '</label>'; |
| 112 | } |
| 113 | else { |
| 114 | $el['label'] = "<label>{$el['label']}</label>"; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | // Display-only (frozen) elements |
| 119 | if (!empty($el['frozen'])) { |
| 120 | if ($element->getAttribute('data-api-entity') && $element->getAttribute('data-entity-value')) { |
| 121 | $this->renderFrozenEntityRef($el, $element); |
| 122 | } |
| 123 | elseif ($element->getAttribute('type') == 'text' && $element->getAttribute('formatType')) { |
| 124 | list($date, $time) = CRM_Utils_Date::setDateDefaults($element->getValue(), $element->getAttribute('formatType'), $element->getAttribute('format'), $element->getAttribute('timeformat')); |
| 125 | $date .= ($element->getAttribute('timeformat')) ? " $time" : ''; |
| 126 | $el['html'] = $date . '<input type="hidden" value="' . $element->getValue() . '" name="' . $element->getAttribute('name') . '">'; |
| 127 | } |
| 128 | |
| 129 | $el['html'] = '<span class="crm-frozen-field">' . $el['html'] . '</span>'; |
| 130 | } |
| 131 | // Active form elements |
| 132 | else { |
| 133 | if ($element->getType() == 'select' && $element->getAttribute('data-option-edit-path')) { |
| 134 | $this->addOptionsEditLink($el, $element); |
| 135 | } |
| 136 | |
| 137 | if ($element->getType() == 'group' && $element->getAttribute('allowClear')) { |
| 138 | $this->appendUnselectButton($el, $element); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | return $el; |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Update the attributes of this element and add a few CiviCRM |
| 147 | * based attributes so we can style this form element better |
| 148 | * |
| 149 | * |
| 150 | * @param HTML_QuickForm_element $element |
| 151 | * @param bool $required |
| 152 | * Whether an element is required. |
| 153 | * @param string $error |
| 154 | * Error associated with the element. |
| 155 | * |
| 156 | */ |
| 157 | public static function updateAttributes(&$element, $required, $error) { |
| 158 | // lets create an id for all input elements, so we can generate nice label tags |
| 159 | // to make it nice and clean, we'll just use the elementName if it is non null |
| 160 | $attributes = array(); |
| 161 | if (!$element->getAttribute('id')) { |
| 162 | $name = $element->getAttribute('name'); |
| 163 | if ($name) { |
| 164 | $attributes['id'] = str_replace(array(']', '['), |
| 165 | array('', '_'), |
| 166 | $name |
| 167 | ); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | $class = $element->getAttribute('class'); |
| 172 | $type = $element->getType(); |
| 173 | if (!$class) { |
| 174 | if ($type == 'text') { |
| 175 | $size = $element->getAttribute('size'); |
| 176 | if (!empty($size)) { |
| 177 | $class = CRM_Utils_Array::value($size, self::$_sizeMapper); |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | if ($type == 'select' && $element->getAttribute('multiple')) { |
| 183 | $type = 'multiselect'; |
| 184 | } |
| 185 | // Add widget-specific class |
| 186 | if (!$class || strpos($class, 'crm-form-') === FALSE) { |
| 187 | $class = ($class ? "$class " : '') . 'crm-form-' . $type; |
| 188 | } |
| 189 | elseif (strpos($class, 'crm-form-entityref') !== FALSE) { |
| 190 | self::preProcessEntityRef($element); |
| 191 | } |
| 192 | elseif (strpos($class, 'crm-form-contact-reference') !== FALSE) { |
| 193 | self::preprocessContactReference($element); |
| 194 | } |
| 195 | |
| 196 | if ($required) { |
| 197 | $class .= ' required'; |
| 198 | } |
| 199 | |
| 200 | if ($error) { |
| 201 | $class .= ' error'; |
| 202 | } |
| 203 | |
| 204 | $attributes['class'] = $class; |
| 205 | $element->updateAttributes($attributes); |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Convert IDs to values and format for display. |
| 210 | * |
| 211 | * @param HTML_QuickForm_element $field |
| 212 | */ |
| 213 | public static function preProcessEntityRef($field) { |
| 214 | $val = $field->getValue(); |
| 215 | // Temporarily convert string values to an array |
| 216 | if (!is_array($val)) { |
| 217 | // Try to auto-detect method of serialization |
| 218 | $val = strpos($val, ',') ? explode(',', str_replace(', ', ',', $val)) : (array) CRM_Utils_Array::explodePadded($val); |
| 219 | } |
| 220 | if ($val) { |
| 221 | $entity = $field->getAttribute('data-api-entity'); |
| 222 | // Get api params, ensure it is an array |
| 223 | $params = $field->getAttribute('data-api-params'); |
| 224 | $params = $params ? json_decode($params, TRUE) : array(); |
| 225 | $result = civicrm_api3($entity, 'getlist', array('id' => $val) + $params); |
| 226 | if ($field->isFrozen()) { |
| 227 | // Prevent js from treating frozen entityRef as a "live" field |
| 228 | $field->removeAttribute('class'); |
| 229 | } |
| 230 | if (!empty($result['values'])) { |
| 231 | $field->setAttribute('data-entity-value', json_encode($result['values'])); |
| 232 | } |
| 233 | // CRM-15803 - Remove invalid values |
| 234 | $val = array_intersect($val, CRM_Utils_Array::collect('id', $result['values'])); |
| 235 | } |
| 236 | // Convert array values back to a string |
| 237 | $field->setValue(implode(',', $val)); |
| 238 | } |
| 239 | |
| 240 | /** |
| 241 | * Render entity references as text. |
| 242 | * If user has permission, format as link (for now limited to contacts). |
| 243 | * |
| 244 | * @param array $el |
| 245 | * @param HTML_QuickForm_element $field |
| 246 | */ |
| 247 | public function renderFrozenEntityRef(&$el, $field) { |
| 248 | $entity = $field->getAttribute('data-api-entity'); |
| 249 | $vals = json_decode($field->getAttribute('data-entity-value'), TRUE); |
| 250 | $display = array(); |
| 251 | |
| 252 | // Custom fields of type contactRef store their data in a slightly different format |
| 253 | if ($field->getAttribute('data-crm-custom') && $entity == 'contact') { |
| 254 | $vals = array(array('id' => $vals['id'], 'label' => $vals['text'])); |
| 255 | } |
| 256 | |
| 257 | foreach ($vals as $val) { |
| 258 | // Format contact as link |
| 259 | if ($entity == 'contact' && CRM_Contact_BAO_Contact_Permission::allow($val['id'], CRM_Core_Permission::VIEW)) { |
| 260 | $url = CRM_Utils_System::url("civicrm/contact/view", array('reset' => 1, 'cid' => $val['id'])); |
| 261 | $val['label'] = '<a class="view-' . $entity . ' no-popup" href="' . $url . '" title="' . ts('View Contact') . '">' . $val['label'] . '</a>'; |
| 262 | } |
| 263 | $display[] = $val['label']; |
| 264 | } |
| 265 | |
| 266 | $el['html'] = implode('; ', $display) . '<input type="hidden" value="' . $field->getValue() . '" name="' . $field->getAttribute('name') . '">'; |
| 267 | } |
| 268 | |
| 269 | /** |
| 270 | * Pre-fill contact name for a custom field of type ContactReference |
| 271 | * |
| 272 | * Todo: Migrate contact reference fields to use EntityRef |
| 273 | * |
| 274 | * @param HTML_QuickForm_element $field |
| 275 | */ |
| 276 | public static function preprocessContactReference($field) { |
| 277 | $val = $field->getValue(); |
| 278 | if ($val && is_numeric($val)) { |
| 279 | |
| 280 | $list = array_keys(CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, |
| 281 | 'contact_reference_options' |
| 282 | ), '1'); |
| 283 | |
| 284 | $return = array_unique(array_merge(array('sort_name'), $list)); |
| 285 | |
| 286 | $contact = civicrm_api('contact', 'getsingle', array('id' => $val, 'return' => $return, 'version' => 3)); |
| 287 | |
| 288 | if (!empty($contact['id'])) { |
| 289 | $view = array(); |
| 290 | foreach ($return as $fld) { |
| 291 | if (!empty($contact[$fld])) { |
| 292 | $view[] = $contact[$fld]; |
| 293 | } |
| 294 | } |
| 295 | $field->setAttribute('data-entity-value', json_encode(array( |
| 296 | 'id' => $contact['id'], |
| 297 | 'text' => implode(' :: ', $view), |
| 298 | ))); |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | /** |
| 304 | * @param array $el |
| 305 | * @param HTML_QuickForm_element $field |
| 306 | */ |
| 307 | public function addOptionsEditLink(&$el, $field) { |
| 308 | if (CRM_Core_Permission::check('administer CiviCRM')) { |
| 309 | // NOTE: $path is used on the client-side to know which option lists need rebuilding, |
| 310 | // that's why we need that bit of data both in the link and in the form element |
| 311 | $path = $field->getAttribute('data-option-edit-path'); |
| 312 | // NOTE: If we ever needed to support arguments in this link other than reset=1 we could split $path here if it contains a ? |
| 313 | $url = CRM_Utils_System::url($path, 'reset=1'); |
| 314 | $el['html'] .= ' <a href="' . $url . '" class="crm-option-edit-link medium-popup crm-hover-button" target="_blank" title="' . ts('Edit Options') . '" data-option-edit-path="' . $path . '"><span class="icon ui-icon-wrench"></span></a>'; |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * @param array $el |
| 320 | * @param HTML_QuickForm_element $field |
| 321 | */ |
| 322 | public function appendUnselectButton(&$el, $field) { |
| 323 | // Initially hide if not needed |
| 324 | // Note: visibility:hidden prevents layout jumping around unlike display:none |
| 325 | $display = $field->getValue() !== NULL ? '' : ' style="visibility:hidden;"'; |
| 326 | $el['html'] .= ' <a href="#" class="crm-hover-button crm-clear-link"' . $display . ' title="' . ts('Clear') . '"><span class="icon ui-icon-close"></span></a>'; |
| 327 | } |
| 328 | |
| 329 | } |