| Total Complexity | 101 |
| Total Lines | 617 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like FieldAuthor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use FieldAuthor, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 15 | class FieldAuthor extends Field implements ExportableField |
||
| 16 | { |
||
| 17 | public function __construct() |
||
| 18 | { |
||
| 19 | parent::__construct(); |
||
| 20 | $this->_name = __('Author'); |
||
|
|
|||
| 21 | $this->_required = true; |
||
| 22 | |||
| 23 | $this->set('author_types', array()); |
||
| 24 | } |
||
| 25 | |||
| 26 | /*------------------------------------------------------------------------- |
||
| 27 | Definition: |
||
| 28 | -------------------------------------------------------------------------*/ |
||
| 29 | |||
| 30 | public function canToggle() |
||
| 31 | { |
||
| 32 | return ($this->get('allow_multiple_selection') === 'yes' ? false : true); |
||
| 33 | } |
||
| 34 | |||
| 35 | public function getToggleStates() |
||
| 36 | { |
||
| 37 | $authors = AuthorManager::fetch(); |
||
| 38 | |||
| 39 | $states = array(); |
||
| 40 | foreach ($authors as $a) { |
||
| 41 | $states[$a->get('id')] = $a->getFullName(); |
||
| 42 | } |
||
| 43 | |||
| 44 | return $states; |
||
| 45 | } |
||
| 46 | |||
| 47 | public function toggleFieldData(array $data, $newState, $entry_id = null) |
||
| 48 | { |
||
| 49 | $data['author_id'] = $newState; |
||
| 50 | return $data; |
||
| 51 | } |
||
| 52 | |||
| 53 | public function canFilter() |
||
| 56 | } |
||
| 57 | |||
| 58 | public function isSortable() |
||
| 59 | { |
||
| 60 | return $this->canToggle(); |
||
| 61 | } |
||
| 62 | |||
| 63 | public function allowDatasourceOutputGrouping() |
||
| 67 | } |
||
| 68 | |||
| 69 | public function allowDatasourceParamOutput() |
||
| 70 | { |
||
| 71 | return true; |
||
| 72 | } |
||
| 73 | |||
| 74 | public function fetchSuggestionTypes() |
||
| 75 | { |
||
| 76 | return array('static'); |
||
| 77 | } |
||
| 78 | |||
| 79 | /*------------------------------------------------------------------------- |
||
| 80 | Setup: |
||
| 81 | -------------------------------------------------------------------------*/ |
||
| 82 | |||
| 83 | public function createTable() |
||
| 84 | { |
||
| 85 | return Symphony::Database()->query( |
||
| 86 | "CREATE TABLE IF NOT EXISTS `tbl_entries_data_" . $this->get('id') ."` ( |
||
| 87 | `id` int(11) unsigned NOT null auto_increment, |
||
| 88 | `entry_id` int(11) unsigned NOT null, |
||
| 89 | `author_id` int(11) unsigned null, |
||
| 90 | PRIMARY KEY (`id`), |
||
| 91 | UNIQUE KEY `author` (`entry_id`, `author_id`), |
||
| 92 | KEY `author_id` (`author_id`) |
||
| 93 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;" |
||
| 94 | ); |
||
| 95 | } |
||
| 96 | |||
| 97 | /*------------------------------------------------------------------------- |
||
| 98 | Utilities: |
||
| 99 | -------------------------------------------------------------------------*/ |
||
| 100 | |||
| 101 | public function set($field, $value) |
||
| 102 | { |
||
| 103 | if ($field === 'author_types' && !is_array($value)) { |
||
| 104 | $value = explode(',', $value); |
||
| 105 | } |
||
| 106 | |||
| 107 | $this->_settings[$field] = $value; |
||
| 108 | } |
||
| 109 | |||
| 110 | /** |
||
| 111 | * Determines based on the input value whether we want to filter the Author |
||
| 112 | * field by ID or by the Author's Username |
||
| 113 | * |
||
| 114 | * @since Symphony 2.2 |
||
| 115 | * @param string $value |
||
| 116 | * @return string |
||
| 117 | * Either `author_id` or `username` |
||
| 118 | */ |
||
| 119 | private static function __parseFilter($value) |
||
| 120 | { |
||
| 121 | return is_numeric($value) ? 'author_id' : 'username'; |
||
| 122 | } |
||
| 123 | |||
| 124 | /*------------------------------------------------------------------------- |
||
| 125 | Settings: |
||
| 126 | -------------------------------------------------------------------------*/ |
||
| 127 | |||
| 128 | public function findDefaults(array &$settings) |
||
| 129 | { |
||
| 130 | if (!isset($settings['allow_multiple_selection'])) { |
||
| 131 | $settings['allow_multiple_selection'] = 'no'; |
||
| 132 | } |
||
| 133 | |||
| 134 | if (!isset($settings['author_types'])) { |
||
| 135 | $settings['author_types'] = array('developer', 'manager', 'author'); |
||
| 136 | } |
||
| 137 | } |
||
| 138 | |||
| 139 | public function displaySettingsPanel(XMLElement &$wrapper, $errors = null) |
||
| 140 | { |
||
| 141 | parent::displaySettingsPanel($wrapper, $errors); |
||
| 142 | |||
| 143 | // Author types |
||
| 144 | $label = Widget::Label(__('Author types')); |
||
| 145 | $types = $this->get('author_types'); |
||
| 146 | $options = array( |
||
| 147 | array('author', empty($types) ? true : in_array('author', $types), __('Author')), |
||
| 148 | array('manager', empty($types) ? true : in_array('manager', $types), __('Manager')), |
||
| 149 | array('developer', empty($types) ? true : in_array('developer', $types), __('Developer')) |
||
| 150 | ); |
||
| 151 | $label->appendChild( |
||
| 152 | Widget::Select('fields['.$this->get('sortorder').'][author_types][]', $options, array( |
||
| 153 | 'multiple' => 'multiple' |
||
| 154 | )) |
||
| 155 | ); |
||
| 156 | |||
| 157 | if (isset($errors['author_types'])) { |
||
| 158 | $wrapper->appendChild(Widget::Error($label, $errors['author_types'])); |
||
| 159 | } else { |
||
| 160 | $wrapper->appendChild($label); |
||
| 161 | } |
||
| 162 | |||
| 163 | // Options |
||
| 164 | $div = new XMLElement('div', null, array('class' => 'two columns')); |
||
| 165 | |||
| 166 | // Allow multiple selection |
||
| 167 | $this->createCheckboxSetting($div, 'allow_multiple_selection', __('Allow selection of multiple authors')); |
||
| 168 | |||
| 169 | // Default to current logged in user |
||
| 170 | $this->createCheckboxSetting($div, 'default_to_current_user', __('Select current user by default')); |
||
| 171 | |||
| 172 | // Requirements and table display |
||
| 173 | $wrapper->appendChild($div); |
||
| 174 | $this->appendStatusFooter($wrapper); |
||
| 175 | } |
||
| 176 | |||
| 177 | public function checkFields(array &$errors, $checkForDuplicates = true) |
||
| 178 | { |
||
| 179 | parent::checkFields($errors, $checkForDuplicates); |
||
| 180 | |||
| 181 | $types = $this->get('author_types'); |
||
| 182 | |||
| 183 | if (empty($types)) { |
||
| 184 | $errors['author_types'] = __('This is a required field.'); |
||
| 185 | } |
||
| 186 | |||
| 187 | return (is_array($errors) && !empty($errors) ? self::__ERROR__ : self::__OK__); |
||
| 188 | } |
||
| 189 | |||
| 190 | public function commit() |
||
| 191 | { |
||
| 192 | if (!parent::commit()) { |
||
| 193 | return false; |
||
| 194 | } |
||
| 195 | |||
| 196 | $id = $this->get('id'); |
||
| 197 | |||
| 198 | if ($id === false) { |
||
| 199 | return false; |
||
| 200 | } |
||
| 201 | |||
| 202 | $fields = array(); |
||
| 203 | |||
| 204 | $fields['allow_multiple_selection'] = ($this->get('allow_multiple_selection') ? $this->get('allow_multiple_selection') : 'no'); |
||
| 205 | $fields['default_to_current_user'] = ($this->get('default_to_current_user') ? $this->get('default_to_current_user') : 'no'); |
||
| 206 | |||
| 207 | if ($this->get('author_types') != '') { |
||
| 208 | $fields['author_types'] = implode(',', $this->get('author_types')); |
||
| 209 | } |
||
| 210 | |||
| 211 | return FieldManager::saveSettings($id, $fields); |
||
| 212 | } |
||
| 213 | |||
| 214 | /*------------------------------------------------------------------------- |
||
| 215 | Publish: |
||
| 216 | -------------------------------------------------------------------------*/ |
||
| 217 | |||
| 218 | public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null) |
||
| 219 | { |
||
| 220 | $value = isset($data['author_id']) ? $data['author_id'] : null; |
||
| 221 | |||
| 222 | if ($this->get('default_to_current_user') === 'yes' && empty($data) && empty($_POST)) { |
||
| 223 | $value = array(Symphony::Author()->get('id')); |
||
| 224 | } |
||
| 225 | |||
| 226 | if (!is_array($value)) { |
||
| 227 | $value = array($value); |
||
| 228 | } |
||
| 229 | |||
| 230 | $options = array(); |
||
| 231 | |||
| 232 | if ($this->get('required') !== 'yes') { |
||
| 233 | $options[] = array(null, false, null); |
||
| 234 | } |
||
| 235 | |||
| 236 | // Custom where to only show Authors based off the Author Types setting |
||
| 237 | $types = $this->get('author_types'); |
||
| 238 | |||
| 239 | if (!empty($types)) { |
||
| 240 | $types = implode('","', $this->get('author_types')); |
||
| 241 | $where = 'user_type IN ("' . $types . '")'; |
||
| 242 | } |
||
| 243 | |||
| 244 | $authors = AuthorManager::fetch('id', 'ASC', null, null, $where); |
||
| 245 | $found = false; |
||
| 246 | |||
| 247 | foreach ($authors as $a) { |
||
| 248 | if (in_array($a->get('id'), $value)) { |
||
| 249 | $found = true; |
||
| 250 | } |
||
| 251 | |||
| 252 | $options[] = array($a->get('id'), in_array($a->get('id'), $value), $a->getFullName()); |
||
| 253 | } |
||
| 254 | |||
| 255 | // Ensure the selected Author is included in the options (incase |
||
| 256 | // the settings change after the original entry was created) |
||
| 257 | if (!$found && !is_null($value)) { |
||
| 258 | $authors = AuthorManager::fetchByID($value); |
||
| 259 | |||
| 260 | foreach ($authors as $a) { |
||
| 261 | $options[] = array($a->get('id'), in_array($a->get('id'), $value), $a->getFullName()); |
||
| 262 | } |
||
| 263 | } |
||
| 264 | |||
| 265 | $fieldname = 'fields'.$fieldnamePrefix.'['.$this->get('element_name').']'.$fieldnamePostfix; |
||
| 266 | |||
| 267 | if ($this->get('allow_multiple_selection') === 'yes') { |
||
| 268 | $fieldname .= '[]'; |
||
| 269 | } |
||
| 270 | |||
| 271 | $label = Widget::Label($this->get('label')); |
||
| 272 | |||
| 273 | if ($this->get('required') !== 'yes') { |
||
| 274 | $label->appendChild(new XMLElement('i', __('Optional'))); |
||
| 275 | } |
||
| 276 | |||
| 277 | $label->appendChild(Widget::Select($fieldname, $options, ($this->get('allow_multiple_selection') === 'yes' ? array('multiple' => 'multiple') : null))); |
||
| 278 | |||
| 279 | if ($flagWithError != null) { |
||
| 280 | $wrapper->appendChild(Widget::Error($label, $flagWithError)); |
||
| 281 | } else { |
||
| 282 | $wrapper->appendChild($label); |
||
| 283 | } |
||
| 284 | } |
||
| 285 | |||
| 286 | public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null) |
||
| 287 | { |
||
| 288 | $status = self::__OK__; |
||
| 289 | |||
| 290 | if (!is_array($data) && !empty($data)) { |
||
| 291 | return array('author_id' => $data); |
||
| 292 | } |
||
| 293 | |||
| 294 | if (empty($data)) { |
||
| 295 | return null; |
||
| 296 | } |
||
| 297 | |||
| 298 | $result = array(); |
||
| 299 | |||
| 300 | foreach ($data as $id) { |
||
| 301 | $result['author_id'][] = $id; |
||
| 302 | } |
||
| 303 | |||
| 304 | return $result; |
||
| 305 | } |
||
| 306 | |||
| 307 | /*------------------------------------------------------------------------- |
||
| 308 | Output: |
||
| 309 | -------------------------------------------------------------------------*/ |
||
| 310 | |||
| 311 | public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null) |
||
| 312 | { |
||
| 313 | if (!is_array($data['author_id'])) { |
||
| 314 | $data['author_id'] = array($data['author_id']); |
||
| 315 | } |
||
| 316 | |||
| 317 | $list = new XMLElement($this->get('element_name')); |
||
| 318 | $authors = AuthorManager::fetchByID($data['author_id']); |
||
| 319 | |||
| 320 | foreach ($authors as $author) { |
||
| 321 | if (is_null($author)) { |
||
| 322 | continue; |
||
| 323 | } |
||
| 324 | |||
| 325 | $list->appendChild(new XMLElement( |
||
| 326 | 'item', |
||
| 327 | $author->getFullName(), |
||
| 328 | array( |
||
| 329 | 'id' => (string)$author->get('id'), |
||
| 330 | 'handle' => Lang::createHandle($author->getFullName()), |
||
| 331 | 'username' => General::sanitize($author->get('username')) |
||
| 332 | ) |
||
| 333 | )); |
||
| 334 | } |
||
| 335 | |||
| 336 | $wrapper->appendChild($list); |
||
| 337 | } |
||
| 338 | |||
| 339 | public function prepareTextValue($data, $entry_id = null) |
||
| 340 | { |
||
| 341 | $value = $this->prepareExportValue($data, ExportableField::LIST_OF + ExportableField::VALUE, $entry_id); |
||
| 342 | return General::sanitize(implode(', ', $value)); |
||
| 343 | } |
||
| 344 | |||
| 345 | public function getParameterPoolValue(array $data, $entry_id = null) |
||
| 348 | } |
||
| 349 | |||
| 350 | /*------------------------------------------------------------------------- |
||
| 351 | Export: |
||
| 352 | -------------------------------------------------------------------------*/ |
||
| 353 | |||
| 354 | /** |
||
| 355 | * Return a list of supported export modes for use with `prepareExportValue`. |
||
| 356 | * |
||
| 357 | * @return array |
||
| 358 | */ |
||
| 359 | public function getExportModes() |
||
| 373 | ); |
||
| 374 | } |
||
| 375 | |||
| 376 | /** |
||
| 377 | * Give the field some data and ask it to return a value using one of many |
||
| 378 | * possible modes. |
||
| 379 | * |
||
| 380 | * @param mixed $data |
||
| 381 | * @param integer $mode |
||
| 382 | * @param integer $entry_id |
||
| 383 | * @return array|null |
||
| 384 | */ |
||
| 385 | public function prepareExportValue($data, $mode, $entry_id = null) |
||
| 386 | { |
||
| 387 | $modes = (object)$this->getExportModes(); |
||
| 388 | |||
| 389 | // Make sure we have an array to work with: |
||
| 390 | if (isset($data['author_id']) && is_array($data['author_id']) === false) { |
||
| 391 | $data['author_id'] = array( |
||
| 392 | $data['author_id'] |
||
| 393 | ); |
||
| 394 | } |
||
| 395 | |||
| 396 | // Return the author IDs: |
||
| 397 | if ($mode === $modes->listAuthor || $mode === $modes->getPostdata) { |
||
| 398 | return isset($data['author_id']) |
||
| 399 | ? $data['author_id'] |
||
| 400 | : array(); |
||
| 401 | } |
||
| 402 | |||
| 403 | // All other modes require full data: |
||
| 404 | $authors = isset($data['author_id']) |
||
| 405 | ? AuthorManager::fetchByID($data['author_id']) |
||
| 406 | : array(); |
||
| 407 | $items = array(); |
||
| 408 | |||
| 409 | foreach ($authors as $author) { |
||
| 410 | if (is_null($author)) { |
||
| 411 | continue; |
||
| 412 | } |
||
| 413 | |||
| 414 | if ($mode === $modes->listAuthorObject) { |
||
| 415 | $items[] = $author; |
||
| 416 | } elseif ($mode === $modes->listValue) { |
||
| 417 | $items[] = $author->getFullName(); |
||
| 418 | } elseif ($mode === $modes->listAuthorToValue) { |
||
| 419 | $items[$data['author_id']] = $author->getFullName(); |
||
| 420 | } |
||
| 421 | } |
||
| 422 | |||
| 423 | return $items; |
||
| 424 | } |
||
| 425 | |||
| 426 | /*------------------------------------------------------------------------- |
||
| 427 | Filtering: |
||
| 428 | -------------------------------------------------------------------------*/ |
||
| 429 | |||
| 430 | public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = false) |
||
| 431 | { |
||
| 432 | $field_id = $this->get('id'); |
||
| 433 | |||
| 434 | if (self::isFilterRegex($data[0])) { |
||
| 435 | $this->_key++; |
||
| 436 | |||
| 437 | if (preg_match('/^regexp:/i', $data[0])) { |
||
| 438 | $pattern = preg_replace('/^regexp:\s*/i', null, $this->cleanValue($data[0])); |
||
| 439 | $regex = 'REGEXP'; |
||
| 440 | } else { |
||
| 441 | $pattern = preg_replace('/^not-?regexp:\s*/i', null, $this->cleanValue($data[0])); |
||
| 442 | $regex = 'NOT REGEXP'; |
||
| 443 | } |
||
| 444 | |||
| 445 | if (strlen($pattern) == 0) { |
||
| 446 | return; |
||
| 447 | } |
||
| 448 | |||
| 449 | $joins .= " |
||
| 450 | LEFT JOIN |
||
| 451 | `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key} |
||
| 452 | ON (e.id = t{$field_id}_{$this->_key}.entry_id) |
||
| 453 | JOIN |
||
| 454 | `tbl_authors` AS t{$field_id}_{$this->_key}_authors |
||
| 455 | ON (t{$field_id}_{$this->_key}.author_id = t{$field_id}_{$this->_key}_authors.id) |
||
| 456 | "; |
||
| 457 | $where .= " |
||
| 458 | AND ( |
||
| 459 | t{$field_id}_{$this->_key}.author_id {$regex} '{$pattern}' |
||
| 460 | OR t{$field_id}_{$this->_key}_authors.username {$regex} '{$pattern}' |
||
| 461 | OR CONCAT_WS(' ', |
||
| 462 | t{$field_id}_{$this->_key}_authors.first_name, |
||
| 463 | t{$field_id}_{$this->_key}_authors.last_name |
||
| 464 | ) {$regex} '{$pattern}' |
||
| 465 | ) |
||
| 466 | "; |
||
| 467 | } elseif (self::isFilterSQL($data[0])) { |
||
| 468 | $this->buildFilterSQL($data[0], array('username', 'first_name', 'last_name'), $joins, $where); |
||
| 469 | } elseif ($andOperation) { |
||
| 470 | foreach ($data as $value) { |
||
| 471 | $this->_key++; |
||
| 472 | $value = $this->cleanValue($value); |
||
| 473 | |||
| 474 | if (self::__parseFilter($value) == "author_id") { |
||
| 475 | $where .= " |
||
| 476 | AND t{$field_id}_{$this->_key}.author_id = '{$value}' |
||
| 477 | "; |
||
| 478 | $joins .= " |
||
| 479 | LEFT JOIN |
||
| 480 | `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key} |
||
| 481 | ON (e.id = t{$field_id}_{$this->_key}.entry_id) |
||
| 482 | "; |
||
| 483 | } else { |
||
| 484 | $joins .= " |
||
| 485 | LEFT JOIN |
||
| 486 | `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key} |
||
| 487 | ON (e.id = t{$field_id}_{$this->_key}.entry_id) |
||
| 488 | JOIN |
||
| 489 | `tbl_authors` AS t{$field_id}_{$this->_key}_authors |
||
| 490 | ON (t{$field_id}_{$this->_key}.author_id = t{$field_id}_{$this->_key}_authors.id) |
||
| 491 | "; |
||
| 492 | $where .= " |
||
| 493 | AND ( |
||
| 494 | t{$field_id}_{$this->_key}_authors.username = '{$value}' |
||
| 495 | OR CONCAT_WS(' ', |
||
| 496 | t{$field_id}_{$this->_key}_authors.first_name, |
||
| 497 | t{$field_id}_{$this->_key}_authors.last_name |
||
| 498 | ) = '{$value}' |
||
| 499 | ) |
||
| 500 | "; |
||
| 501 | } |
||
| 502 | } |
||
| 503 | } else { |
||
| 504 | if (!is_array($data)) { |
||
| 505 | $data = array($data); |
||
| 506 | } |
||
| 507 | |||
| 508 | foreach ($data as &$value) { |
||
| 509 | $value = $this->cleanValue($value); |
||
| 510 | } |
||
| 511 | |||
| 512 | $this->_key++; |
||
| 513 | $data = implode("', '", $data); |
||
| 514 | $joins .= " |
||
| 515 | LEFT JOIN |
||
| 516 | `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key} |
||
| 517 | ON (e.id = t{$field_id}_{$this->_key}.entry_id) |
||
| 518 | JOIN |
||
| 519 | `tbl_authors` AS t{$field_id}_{$this->_key}_authors |
||
| 520 | ON (t{$field_id}_{$this->_key}.author_id = t{$field_id}_{$this->_key}_authors.id) |
||
| 521 | "; |
||
| 522 | $where .= " |
||
| 523 | AND ( |
||
| 524 | t{$field_id}_{$this->_key}.author_id IN ('{$data}') |
||
| 525 | OR |
||
| 526 | t{$field_id}_{$this->_key}_authors.username IN ('{$data}') |
||
| 527 | OR CONCAT_WS(' ', |
||
| 528 | t{$field_id}_{$this->_key}_authors.first_name, |
||
| 529 | t{$field_id}_{$this->_key}_authors.last_name |
||
| 530 | ) IN ('{$data}') |
||
| 531 | ) |
||
| 532 | "; |
||
| 533 | } |
||
| 534 | |||
| 535 | return true; |
||
| 536 | } |
||
| 537 | |||
| 538 | /*------------------------------------------------------------------------- |
||
| 539 | Sorting: |
||
| 540 | -------------------------------------------------------------------------*/ |
||
| 541 | |||
| 542 | public function buildSortingSQL(&$joins, &$where, &$sort, $order = 'ASC') |
||
| 543 | { |
||
| 544 | if ($this->isRandomOrder($order)) { |
||
| 545 | $sort = 'ORDER BY RAND()'; |
||
| 546 | } else { |
||
| 547 | $joins .= " |
||
| 548 | LEFT OUTER JOIN `tbl_entries_data_".$this->get('id')."` AS `ed` ON (`e`.`id` = `ed`.`entry_id`) |
||
| 549 | LEFT OUTER JOIN `tbl_authors` AS `a` ON (ed.author_id = a.id) |
||
| 550 | "; |
||
| 551 | $sort = sprintf('ORDER BY `a`.`first_name` %1$s, `a`.`last_name` %1$s, `e`.`id` %1$s', $order); |
||
| 552 | } |
||
| 553 | } |
||
| 554 | |||
| 555 | public function buildSortingSelectSQL($sort, $order = 'ASC') |
||
| 561 | } |
||
| 562 | |||
| 563 | /*------------------------------------------------------------------------- |
||
| 564 | Events: |
||
| 565 | -------------------------------------------------------------------------*/ |
||
| 566 | |||
| 567 | public function getExampleFormMarkup() |
||
| 592 | } |
||
| 593 | |||
| 594 | /*------------------------------------------------------------------------- |
||
| 595 | Grouping: |
||
| 596 | -------------------------------------------------------------------------*/ |
||
| 597 | |||
| 598 | public function groupRecords($records) |
||
| 632 | } |
||
| 633 | } |
||
| 634 |