| Total Complexity | 93 | 
| Total Lines | 560 | 
| Duplicated Lines | 0 % | 
| Changes | 1 | ||
| Bugs | 0 | Features | 1 | 
Complex classes like FieldSelect 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 FieldSelect, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 11 | class FieldSelect extends FieldTagList implements ExportableField, ImportableField  | 
            ||
| 12 | { | 
            ||
| 13 | public function __construct()  | 
            ||
| 14 |     { | 
            ||
| 15 | parent::__construct();  | 
            ||
| 16 |         $this->_name = __('Select Box'); | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 17 | $this->_required = true;  | 
            ||
| 18 | $this->_showassociation = true;  | 
            ||
| 19 | |||
| 20 | // Set default  | 
            ||
| 21 |         $this->set('show_column', 'yes'); | 
            ||
| 22 |         $this->set('location', 'sidebar'); | 
            ||
| 23 |         $this->set('required', 'no'); | 
            ||
| 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 |         $values = preg_split('/,\s*/i', $this->get('static_options'), -1, PREG_SPLIT_NO_EMPTY); | 
            ||
| 38 | |||
| 39 |         if ($this->get('dynamic_options') != '') { | 
            ||
| 40 | $this->findAndAddDynamicOptions($values);  | 
            ||
| 41 | }  | 
            ||
| 42 | |||
| 43 |         $values = array_map('trim', $values); | 
            ||
| 44 | // Fixes issues on PHP5.3. RE: #1773 ^BA  | 
            ||
| 45 |         if (empty($values)) { | 
            ||
| 46 | return $values;  | 
            ||
| 47 | }  | 
            ||
| 48 | |||
| 49 | $states = array_combine($values, $values);  | 
            ||
| 50 | |||
| 51 |         if ($this->get('sort_options') === 'yes') { | 
            ||
| 52 | natsort($states);  | 
            ||
| 53 | }  | 
            ||
| 54 | |||
| 55 | return $states;  | 
            ||
| 56 | }  | 
            ||
| 57 | |||
| 58 | public function toggleFieldData(array $data, $newState, $entry_id = null)  | 
            ||
| 59 |     { | 
            ||
| 60 | $data['value'] = $newState;  | 
            ||
| 61 | $data['handle'] = Lang::createHandle($newState);  | 
            ||
| 62 | |||
| 63 | return $data;  | 
            ||
| 64 | }  | 
            ||
| 65 | |||
| 66 | public function canFilter()  | 
            ||
| 67 |     { | 
            ||
| 68 | return true;  | 
            ||
| 69 | }  | 
            ||
| 70 | |||
| 71 | public function canPrePopulate()  | 
            ||
| 72 |     { | 
            ||
| 73 | return true;  | 
            ||
| 74 | }  | 
            ||
| 75 | |||
| 76 | public function isSortable()  | 
            ||
| 77 |     { | 
            ||
| 78 | return true;  | 
            ||
| 79 | }  | 
            ||
| 80 | |||
| 81 | public function allowDatasourceOutputGrouping()  | 
            ||
| 82 |     { | 
            ||
| 83 | // Grouping follows the same rule as toggling.  | 
            ||
| 84 | return $this->canToggle();  | 
            ||
| 85 | }  | 
            ||
| 86 | |||
| 87 | public function allowDatasourceParamOutput()  | 
            ||
| 88 |     { | 
            ||
| 89 | return true;  | 
            ||
| 90 | }  | 
            ||
| 91 | |||
| 92 | public function requiresSQLGrouping()  | 
            ||
| 93 |     { | 
            ||
| 94 | // SQL grouping follows the opposite rule as toggling.  | 
            ||
| 95 | return !$this->canToggle();  | 
            ||
| 96 | }  | 
            ||
| 97 | |||
| 98 | public function fetchSuggestionTypes()  | 
            ||
| 99 |     { | 
            ||
| 100 |         return array('association', 'static'); | 
            ||
| 101 | }  | 
            ||
| 102 | |||
| 103 | /*-------------------------------------------------------------------------  | 
            ||
| 104 | Setup:  | 
            ||
| 105 | -------------------------------------------------------------------------*/  | 
            ||
| 106 | |||
| 107 | public function createTable()  | 
            ||
| 111 | `id` int(11) unsigned NOT null auto_increment,  | 
            ||
| 112 | `entry_id` int(11) unsigned NOT null,  | 
            ||
| 113 | `handle` varchar(255) default null,  | 
            ||
| 114 | `value` varchar(255) default null,  | 
            ||
| 115 | PRIMARY KEY (`id`),  | 
            ||
| 116 | KEY `entry_id` (`entry_id`),  | 
            ||
| 117 | KEY `handle` (`handle`),  | 
            ||
| 118 | KEY `value` (`value`)  | 
            ||
| 119 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;"  | 
            ||
| 120 | );  | 
            ||
| 121 | }  | 
            ||
| 122 | |||
| 123 | /*-------------------------------------------------------------------------  | 
            ||
| 124 | Utilities:  | 
            ||
| 125 | -------------------------------------------------------------------------*/  | 
            ||
| 126 | |||
| 127 | public function findAndAddDynamicOptions(&$values)  | 
            ||
| 128 |     { | 
            ||
| 129 |         if (!is_array($values)) { | 
            ||
| 130 | $values = array();  | 
            ||
| 131 | }  | 
            ||
| 132 | |||
| 133 | $results = false;  | 
            ||
| 134 | |||
| 135 | // Fixes #1802  | 
            ||
| 136 |         if (!Symphony::Database()->tableExists('tbl_entries_data_' . $this->get('dynamic_options'))) { | 
            ||
| 137 | return;  | 
            ||
| 138 | }  | 
            ||
| 139 | |||
| 140 | // Ensure that the table has a 'value' column  | 
            ||
| 141 |         if ((boolean)Symphony::Database()->fetchVar('Field', 0, sprintf( | 
            ||
| 142 | "SHOW COLUMNS FROM `tbl_entries_data_%d` LIKE '%s'",  | 
            ||
| 143 |             $this->get('dynamic_options'), | 
            ||
| 144 | 'value'  | 
            ||
| 145 |         ))) { | 
            ||
| 146 |             $results = Symphony::Database()->fetchCol('value', sprintf( | 
            ||
| 147 | "SELECT DISTINCT `value`  | 
            ||
| 148 | FROM `tbl_entries_data_%d`  | 
            ||
| 149 | ORDER BY `value` ASC",  | 
            ||
| 150 |                 $this->get('dynamic_options') | 
            ||
| 151 | ));  | 
            ||
| 152 | }  | 
            ||
| 153 | |||
| 154 | // In the case of a Upload field, use 'file' instead of 'value'  | 
            ||
| 155 |         if (($results == false) && (boolean)Symphony::Database()->fetchVar('Field', 0, sprintf( | 
            ||
| 156 | "SHOW COLUMNS FROM `tbl_entries_data_%d` LIKE '%s'",  | 
            ||
| 157 |             $this->get('dynamic_options'), | 
            ||
| 158 | 'file'  | 
            ||
| 159 |         ))) { | 
            ||
| 160 |             $results = Symphony::Database()->fetchCol('file', sprintf( | 
            ||
| 161 | "SELECT DISTINCT `file`  | 
            ||
| 162 | FROM `tbl_entries_data_%d`  | 
            ||
| 163 | ORDER BY `file` ASC",  | 
            ||
| 164 |                 $this->get('dynamic_options') | 
            ||
| 165 | ));  | 
            ||
| 166 | }  | 
            ||
| 167 | |||
| 168 |         if ($results) { | 
            ||
| 169 |             if ($this->get('sort_options') == 'no') { | 
            ||
| 170 | natsort($results);  | 
            ||
| 171 | }  | 
            ||
| 172 | |||
| 173 | $values = array_merge($values, $results);  | 
            ||
| 174 | }  | 
            ||
| 175 | }  | 
            ||
| 176 | |||
| 177 | /*-------------------------------------------------------------------------  | 
            ||
| 178 | Settings:  | 
            ||
| 179 | -------------------------------------------------------------------------*/  | 
            ||
| 180 | |||
| 181 | public function findDefaults(array &$settings)  | 
            ||
| 182 |     { | 
            ||
| 183 |         if (!isset($settings['allow_multiple_selection'])) { | 
            ||
| 184 | $settings['allow_multiple_selection'] = 'no';  | 
            ||
| 185 | }  | 
            ||
| 186 | |||
| 187 |         if (!isset($settings['show_association'])) { | 
            ||
| 188 | $settings['show_association'] = 'no';  | 
            ||
| 189 | }  | 
            ||
| 190 | |||
| 191 |         if (!isset($settings['sort_options'])) { | 
            ||
| 192 | $settings['sort_options'] = 'no';  | 
            ||
| 193 | }  | 
            ||
| 194 | }  | 
            ||
| 195 | |||
| 196 | public function displaySettingsPanel(XMLElement &$wrapper, $errors = null)  | 
            ||
| 197 |     { | 
            ||
| 198 | Field::displaySettingsPanel($wrapper, $errors);  | 
            ||
| 199 | |||
| 200 |         $div = new XMLElement('div', null, array('class' => 'two columns')); | 
            ||
| 201 | |||
| 202 | // Predefined Values  | 
            ||
| 203 |         $label = Widget::Label(__('Static Values')); | 
            ||
| 204 |         $label->setAttribute('class', 'column'); | 
            ||
| 205 |         $label->appendChild(new XMLElement('i', __('Optional'))); | 
            ||
| 206 |         $input = Widget::Input('fields['.$this->get('sortorder').'][static_options]', General::sanitize($this->get('static_options'))); | 
            ||
| 207 | $label->appendChild($input);  | 
            ||
| 208 | $div->appendChild($label);  | 
            ||
| 209 | |||
| 210 | // Dynamic Values  | 
            ||
| 211 | // Only append selected ids, load full section information asynchronously  | 
            ||
| 212 |         $label = Widget::Label(__('Dynamic Values')); | 
            ||
| 213 |         $label->setAttribute('class', 'column'); | 
            ||
| 214 |         $label->appendChild(new XMLElement('i', __('Optional'))); | 
            ||
| 215 | |||
| 216 | $options = array(  | 
            ||
| 217 |             array('', false, __('None')) | 
            ||
| 218 | );  | 
            ||
| 219 | |||
| 220 |         if ($this->get('dynamic_options')) { | 
            ||
| 221 |             $options[] = array($this->get('dynamic_options')); | 
            ||
| 222 | }  | 
            ||
| 223 | |||
| 224 | $label->appendChild(  | 
            ||
| 225 |             Widget::Select('fields['.$this->get('sortorder').'][dynamic_options]', $options, array( | 
            ||
| 226 | 'class' => 'js-fetch-sections'  | 
            ||
| 227 | ))  | 
            ||
| 228 | );  | 
            ||
| 229 | |||
| 230 |         if (isset($errors['dynamic_options'])) { | 
            ||
| 231 | $div->appendChild(Widget::Error($label, $errors['dynamic_options']));  | 
            ||
| 232 |         } else { | 
            ||
| 233 | $div->appendChild($label);  | 
            ||
| 234 | }  | 
            ||
| 235 | |||
| 236 | $wrapper->appendChild($div);  | 
            ||
| 237 | |||
| 238 | // Other settings  | 
            ||
| 239 |         $div = new XMLElement('div', null, array('class' => 'two columns')); | 
            ||
| 240 | |||
| 241 | // Allow selection of multiple items  | 
            ||
| 242 |         $this->createCheckboxSetting($div, 'allow_multiple_selection', __('Allow selection of multiple options')); | 
            ||
| 243 | |||
| 244 | // Sort options?  | 
            ||
| 245 |         $this->createCheckboxSetting($div, 'sort_options', __('Sort all options alphabetically')); | 
            ||
| 246 | |||
| 247 | $wrapper->appendChild($div);  | 
            ||
| 248 | |||
| 249 | // Associations  | 
            ||
| 250 |         $fieldset = new XMLElement('fieldset'); | 
            ||
| 251 | $this->appendAssociationInterfaceSelect($fieldset);  | 
            ||
| 252 | $this->appendShowAssociationCheckbox($fieldset);  | 
            ||
| 253 | $wrapper->appendChild($fieldset);  | 
            ||
| 254 | |||
| 255 | // Requirements and table display  | 
            ||
| 256 | $this->appendStatusFooter($wrapper);  | 
            ||
| 257 | }  | 
            ||
| 258 | |||
| 259 | public function checkFields(array &$errors, $checkForDuplicates = true)  | 
            ||
| 260 |     { | 
            ||
| 261 |         if (!is_array($errors)) { | 
            ||
| 262 | $errors = array();  | 
            ||
| 263 | }  | 
            ||
| 264 | |||
| 265 |         if ($this->get('static_options') == '' && ($this->get('dynamic_options') == '' || $this->get('dynamic_options') == 'none')) { | 
            ||
| 266 |             $errors['dynamic_options'] = __('At least one source must be specified, dynamic or static.'); | 
            ||
| 267 | }  | 
            ||
| 268 | |||
| 269 | Field::checkFields($errors, $checkForDuplicates);  | 
            ||
| 270 | }  | 
            ||
| 271 | |||
| 272 | public function commit()  | 
            ||
| 311 | }  | 
            ||
| 312 | |||
| 313 | /*-------------------------------------------------------------------------  | 
            ||
| 314 | Publish:  | 
            ||
| 315 | -------------------------------------------------------------------------*/  | 
            ||
| 316 | |||
| 317 | public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null)  | 
            ||
| 318 |     { | 
            ||
| 319 | $states = $this->getToggleStates();  | 
            ||
| 320 | $value = isset($data['value']) ? $data['value'] : null;  | 
            ||
| 321 | |||
| 322 |         if (!is_array($value)) { | 
            ||
| 323 | $value = array($value);  | 
            ||
| 324 | }  | 
            ||
| 325 | |||
| 326 | $options = array();  | 
            ||
| 327 |         if ($this->get('required') !== 'yes') { | 
            ||
| 328 | $options[] = array(null, false, null);  | 
            ||
| 329 | }  | 
            ||
| 330 | |||
| 331 |         foreach ($states as $handle => $v) { | 
            ||
| 332 | $options[] = array(General::sanitize($v), in_array($v, $value), General::sanitize($v));  | 
            ||
| 333 | }  | 
            ||
| 334 | |||
| 335 |         $fieldname = 'fields'.$fieldnamePrefix.'['.$this->get('element_name').']'.$fieldnamePostfix; | 
            ||
| 336 | |||
| 337 |         if ($this->get('allow_multiple_selection') === 'yes') { | 
            ||
| 338 | $fieldname .= '[]';  | 
            ||
| 339 | }  | 
            ||
| 340 | |||
| 341 |         $label = Widget::Label($this->get('label')); | 
            ||
| 342 | |||
| 343 |         if ($this->get('required') !== 'yes') { | 
            ||
| 344 |             $label->appendChild(new XMLElement('i', __('Optional'))); | 
            ||
| 345 | }  | 
            ||
| 346 | |||
| 347 |         $label->appendChild(Widget::Select($fieldname, $options, ($this->get('allow_multiple_selection') === 'yes' ? array('multiple' => 'multiple', 'size' => count($options)) : null))); | 
            ||
| 348 | |||
| 349 |         if ($flagWithError != null) { | 
            ||
| 350 | $wrapper->appendChild(Widget::Error($label, $flagWithError));  | 
            ||
| 351 |         } else { | 
            ||
| 352 | $wrapper->appendChild($label);  | 
            ||
| 353 | }  | 
            ||
| 354 | }  | 
            ||
| 355 | |||
| 356 | public function checkPostFieldData($data, &$message, $entry_id = null)  | 
            ||
| 357 |     { | 
            ||
| 358 | return Field::checkPostFieldData($data, $message, $entry_id);  | 
            ||
| 359 | }  | 
            ||
| 360 | |||
| 361 | public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null)  | 
            ||
| 362 |     { | 
            ||
| 363 | $status = self::__OK__;  | 
            ||
| 364 | |||
| 365 |         if (!is_array($data)) { | 
            ||
| 366 | return array(  | 
            ||
| 367 | 'value' => $data,  | 
            ||
| 368 | 'handle' => Lang::createHandle($data)  | 
            ||
| 369 | );  | 
            ||
| 370 | }  | 
            ||
| 371 | |||
| 372 |         if (empty($data)) { | 
            ||
| 373 | return null;  | 
            ||
| 374 | }  | 
            ||
| 375 | |||
| 376 | $result = array(  | 
            ||
| 377 | 'value' => array(),  | 
            ||
| 378 | 'handle' => array()  | 
            ||
| 379 | );  | 
            ||
| 380 | |||
| 381 |         foreach ($data as $value) { | 
            ||
| 382 | $result['value'][] = $value;  | 
            ||
| 383 | $result['handle'][] = Lang::createHandle($value);  | 
            ||
| 384 | }  | 
            ||
| 385 | |||
| 386 | return $result;  | 
            ||
| 387 | }  | 
            ||
| 388 | |||
| 389 | /*-------------------------------------------------------------------------  | 
            ||
| 390 | Output:  | 
            ||
| 391 | -------------------------------------------------------------------------*/  | 
            ||
| 392 | |||
| 393 | public function prepareTextValue($data, $entry_id = null)  | 
            ||
| 394 |     { | 
            ||
| 395 | $value = $this->prepareExportValue($data, ExportableField::LIST_OF + ExportableField::VALUE, $entry_id);  | 
            ||
| 396 | |||
| 397 |         return implode(', ', $value); | 
            ||
| 398 | }  | 
            ||
| 399 | |||
| 400 | /*-------------------------------------------------------------------------  | 
            ||
| 401 | Import:  | 
            ||
| 402 | -------------------------------------------------------------------------*/  | 
            ||
| 403 | |||
| 404 | public function prepareImportValue($data, $mode, $entry_id = null)  | 
            ||
| 405 |     { | 
            ||
| 406 | $message = $status = null;  | 
            ||
| 407 | $modes = (object)$this->getImportModes();  | 
            ||
| 408 | |||
| 409 |         if (!is_array($data)) { | 
            ||
| 410 | $data = array($data);  | 
            ||
| 411 | }  | 
            ||
| 412 | |||
| 413 |         if ($mode === $modes->getValue) { | 
            ||
| 414 |             if ($this->get('allow_multiple_selection') === 'no') { | 
            ||
| 415 |                 $data = array(implode('', $data)); | 
            ||
| 416 | }  | 
            ||
| 417 | |||
| 418 | return $data;  | 
            ||
| 419 |         } elseif ($mode === $modes->getPostdata) { | 
            ||
| 420 | return $this->processRawFieldData($data, $status, $message, true, $entry_id);  | 
            ||
| 421 | }  | 
            ||
| 422 | |||
| 423 | return null;  | 
            ||
| 424 | }  | 
            ||
| 425 | |||
| 426 | /*-------------------------------------------------------------------------  | 
            ||
| 427 | Export:  | 
            ||
| 428 | -------------------------------------------------------------------------*/  | 
            ||
| 429 | |||
| 430 | /**  | 
            ||
| 431 | * Return a list of supported export modes for use with `prepareExportValue`.  | 
            ||
| 432 | *  | 
            ||
| 433 | * @return array  | 
            ||
| 434 | */  | 
            ||
| 435 | public function getExportModes()  | 
            ||
| 436 |     { | 
            ||
| 437 | return array(  | 
            ||
| 438 | 'listHandle' => ExportableField::LIST_OF  | 
            ||
| 439 | + ExportableField::HANDLE,  | 
            ||
| 440 | 'listValue' => ExportableField::LIST_OF  | 
            ||
| 441 | + ExportableField::VALUE,  | 
            ||
| 442 | 'listHandleToValue' => ExportableField::LIST_OF  | 
            ||
| 443 | + ExportableField::HANDLE  | 
            ||
| 444 | + ExportableField::VALUE,  | 
            ||
| 445 | 'getPostdata' => ExportableField::POSTDATA  | 
            ||
| 446 | );  | 
            ||
| 447 | }  | 
            ||
| 448 | |||
| 449 | /**  | 
            ||
| 450 | * Give the field some data and ask it to return a value using one of many  | 
            ||
| 451 | * possible modes.  | 
            ||
| 452 | *  | 
            ||
| 453 | * @param mixed $data  | 
            ||
| 454 | * @param integer $mode  | 
            ||
| 455 | * @param integer $entry_id  | 
            ||
| 456 | * @return array  | 
            ||
| 457 | */  | 
            ||
| 458 | public function prepareExportValue($data, $mode, $entry_id = null)  | 
            ||
| 459 |     { | 
            ||
| 460 | $modes = (object)$this->getExportModes();  | 
            ||
| 461 | |||
| 462 |         if (isset($data['handle']) && is_array($data['handle']) === false) { | 
            ||
| 463 | $data['handle'] = array(  | 
            ||
| 464 | $data['handle']  | 
            ||
| 465 | );  | 
            ||
| 466 | }  | 
            ||
| 467 | |||
| 468 |         if (isset($data['value']) && is_array($data['value']) === false) { | 
            ||
| 469 | $data['value'] = array(  | 
            ||
| 470 | $data['value']  | 
            ||
| 471 | );  | 
            ||
| 472 | }  | 
            ||
| 473 | |||
| 474 | // Handle => Value pairs:  | 
            ||
| 475 |         if ($mode === $modes->listHandleToValue) { | 
            ||
| 476 | return isset($data['handle'], $data['value'])  | 
            ||
| 477 | ? array_combine($data['handle'], $data['value'])  | 
            ||
| 478 | : array();  | 
            ||
| 479 | |||
| 480 | // Array of handles:  | 
            ||
| 481 |         } elseif ($mode === $modes->listHandle) { | 
            ||
| 482 | return isset($data['handle'])  | 
            ||
| 483 | ? $data['handle']  | 
            ||
| 484 | : array();  | 
            ||
| 485 | |||
| 486 | // Array of values:  | 
            ||
| 487 |         } elseif ($mode === $modes->listValue || $mode === $modes->getPostdata) { | 
            ||
| 488 | return isset($data['value'])  | 
            ||
| 489 | ? $data['value']  | 
            ||
| 490 | : array();  | 
            ||
| 491 | }  | 
            ||
| 492 | }  | 
            ||
| 493 | |||
| 494 | /*-------------------------------------------------------------------------  | 
            ||
| 495 | Filtering:  | 
            ||
| 496 | -------------------------------------------------------------------------*/  | 
            ||
| 497 | |||
| 498 | public function displayFilteringOptions(XMLElement &$wrapper)  | 
            ||
| 499 |     { | 
            ||
| 500 | $existing_options = $this->getToggleStates();  | 
            ||
| 501 | |||
| 502 |         if (is_array($existing_options) && !empty($existing_options)) { | 
            ||
| 503 |             $optionlist = new XMLElement('ul'); | 
            ||
| 504 |             $optionlist->setAttribute('class', 'tags'); | 
            ||
| 505 |             $optionlist->setAttribute('data-interactive', 'data-interactive'); | 
            ||
| 506 | |||
| 507 |             foreach ($existing_options as $option) { | 
            ||
| 508 | $optionlist->appendChild(  | 
            ||
| 509 |                     new XMLElement('li', General::sanitize($option)) | 
            ||
| 510 | );  | 
            ||
| 511 | };  | 
            ||
| 512 | |||
| 513 | $wrapper->appendChild($optionlist);  | 
            ||
| 514 | }  | 
            ||
| 515 | }  | 
            ||
| 516 | |||
| 517 | /*-------------------------------------------------------------------------  | 
            ||
| 518 | Grouping:  | 
            ||
| 519 | -------------------------------------------------------------------------*/  | 
            ||
| 520 | |||
| 521 | public function groupRecords($records)  | 
            ||
| 545 | }  | 
            ||
| 546 | |||
| 547 | /*-------------------------------------------------------------------------  | 
            ||
| 548 | Events:  | 
            ||
| 549 | -------------------------------------------------------------------------*/  | 
            ||
| 550 | |||
| 551 | public function getExampleFormMarkup()  | 
            ||
| 571 | }  | 
            ||
| 572 | }  | 
            ||
| 573 |