Webylon 3.1 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • CompositeField
  • FieldGroup
  • FieldSet
  • HiddenFieldSet
  • SelectionGroup
  • Tab
  • TabSet
  • ToggleCompositeField
  1 <?php
  2 /**
  3  * DataObjectSet designed for form fields.
  4  * It extends the DataObjectSet with the ability to get a sequential set of fields.
  5  * @package forms
  6  * @subpackage fields-structural
  7  */
  8 class FieldSet extends DataObjectSet {
  9     
 10     /**
 11      * Cached flat representation of all fields in this set,
 12      * including fields nested in {@link CompositeFields}.
 13      *
 14      * @uses self::collateDataFields()
 15      * @var array
 16      */
 17     protected $sequentialSet;
 18     
 19     /**
 20      * @var array
 21      */
 22     protected $sequentialSaveableSet;
 23     
 24     /**
 25      * @todo Documentation
 26      */
 27     protected $containerField;
 28     
 29     public function __construct($items = null) {
 30         // if the first parameter is not an array, or we have more than one parameter, collate all parameters to an array
 31         // otherwise use the passed array
 32         $itemsArr = (!is_array($items) || count(func_get_args()) > 1) ? func_get_args() : $items;
 33         parent::__construct($itemsArr);
 34         
 35         if(isset($this->items) && count($this->items)) {
 36             foreach($this->items as $item) {
 37                 if(isset($item) && is_a($item, 'FormField')) {
 38                     $item->setContainerFieldSet($this);
 39                 }
 40             }
 41         }
 42         
 43     }
 44     
 45     /**
 46      * Return a sequential set of all fields that have data.  This excludes wrapper composite fields
 47      * as well as heading / help text fields.
 48      */
 49     public function dataFields() {
 50         if(!$this->sequentialSet) $this->collateDataFields($this->sequentialSet);
 51         return $this->sequentialSet;
 52     }
 53     
 54     public function saveableFields() {
 55         if(!$this->sequentialSaveableSet) $this->collateDataFields($this->sequentialSaveableSet, true);
 56         return $this->sequentialSaveableSet;
 57     }
 58     
 59     protected function flushFieldsCache() {
 60         $this->sequentialSet = null;
 61         $this->sequentialSaveableSet = null;
 62     }
 63     
 64     protected function collateDataFields(&$list, $saveableOnly = false) {
 65         foreach($this as $field) {
 66             if($field->isComposite()) $field->collateDataFields($list, $saveableOnly);
 67 
 68             if($saveableOnly) {
 69                 $isIncluded =  ($field->hasData() && !$field->isReadonly() && !$field->isDisabled());
 70             } else {
 71                 $isIncluded =  ($field->hasData());
 72             }
 73             if($isIncluded) {
 74                 $name = $field->Name();
 75                 if(isset($list[$name])) {
 76                     $errSuffix = "";
 77                     if($this->form) $errSuffix = " in your '{$this->form->class}' form called '" . $this->form->Name() . "'";
 78                     else $errSuffix = '';
 79                     user_error("collateDataFields() I noticed that a field called '$name' appears twice$errSuffix.", E_USER_ERROR);
 80                 }
 81                 $list[$name] = $field;
 82             }
 83         }
 84     }
 85     
 86     /**
 87      * Add an extra field to a tab within this fieldset.
 88      * This is most commonly used when overloading getCMSFields()
 89      * 
 90      * @param string $tabName The name of the tab or tabset.  Subtabs can be referred to as TabSet.Tab or TabSet.Tab.Subtab.
 91      * This function will create any missing tabs.
 92      * @param FormField $field The {@link FormField} object to add to the end of that tab.
 93      * @param string $insertBefore The name of the field to insert before.  Optional.
 94      */
 95     public function addFieldToTab($tabName, $field, $insertBefore = null) {
 96         // This is a cache that must be flushed
 97         $this->flushFieldsCache();
 98 
 99         // Find the tab
100         $tab = $this->findOrMakeTab($tabName);
101 
102         // Add the field to the end of this set
103         if($insertBefore) $tab->insertBefore($field, $insertBefore);
104         else $tab->push($field);
105     }
106     
107     /**
108      * Add a number of extra fields to a tab within this fieldset.
109      * This is most commonly used when overloading getCMSFields()
110      * 
111      * @param string $tabName The name of the tab or tabset.  Subtabs can be referred to as TabSet.Tab or TabSet.Tab.Subtab.
112      * This function will create any missing tabs.
113      * @param array $fields An array of {@link FormField} objects.
114      */
115     public function addFieldsToTab($tabName, $fields) {
116         $this->flushFieldsCache();
117 
118         // Find the tab
119         $tab = $this->findOrMakeTab($tabName);
120         
121         // Add the fields to the end of this set
122         foreach($fields as $field) {
123             // Check if a field by the same name exists in this tab
124             if($tab->fieldByName($field->Name())) {
125                 // It exists, so we need to replace the old one
126                 $this->replaceField($field->Name(), $field);
127             } else {
128                 $tab->push($field);
129             }
130         }
131     }   
132 
133     /**
134      * Remove the given field from the given tab in the field.
135      * 
136      * @param string $tabName The name of the tab
137      * @param string $fieldName The name of the field
138      */
139     public function removeFieldFromTab($tabName, $fieldName) {
140         $this->flushFieldsCache();
141 
142         // Find the tab
143         $tab = $this->findOrMakeTab($tabName);
144         $tab->removeByName($fieldName);
145     }
146     
147     /**
148      * Removes a number of fields from a Tab/TabSet within this FieldSet.
149      *
150      * @param string $tabName The name of the Tab or TabSet field
151      * @param array $fields A list of fields, e.g. array('Name', 'Email')
152      */
153     public function removeFieldsFromTab($tabName, $fields) {
154         $this->flushFieldsCache();
155 
156         // Find the tab
157         $tab = $this->findOrMakeTab($tabName);
158         
159         // Add the fields to the end of this set
160         foreach($fields as $field) $tab->removeByName($field);
161     }
162     
163     /**
164      * Remove a field from this FieldSet by Name.
165      * The field could also be inside a CompositeField.
166      * 
167      * @param string $fieldName The name of the field or tab
168      * @param boolean $dataFieldOnly If this is true, then a field will only
169      * be removed if it's a data field.  Dataless fields, such as tabs, will
170      * be left as-is.
171      */
172     public function removeByName($fieldName, $dataFieldOnly = false) {
173         if(!$fieldName) {
174             user_error('FieldSet::removeByName() was called with a blank field name.', E_USER_WARNING);
175         }
176         $this->flushFieldsCache();
177         
178         foreach($this->items as $i => $child) {
179             if(is_object($child)){
180                 if(($child->Name() == $fieldName || $child->Title() == $fieldName) && (!$dataFieldOnly || $child->hasData())) {
181                     array_splice( $this->items, $i, 1 );
182                     break;
183                 } else if($child->isComposite()) {
184                     $child->removeByName($fieldName, $dataFieldOnly);
185                 }
186             }
187         }
188     }
189     
190     /**
191      * Replace a single field with another.  Ignores dataless fields such as Tabs and TabSets
192      *
193      * @param string $fieldName The name of the field to replace
194      * @param FormField $newField The field object to replace with
195      * @return boolean TRUE field was successfully replaced
196      *                   FALSE field wasn't found, nothing changed
197      */
198     public function replaceField($fieldName, $newField) {
199         $this->flushFieldsCache();
200         foreach($this->items as $i => $field) {
201             if(is_object($field)) {
202                 if($field->Name() == $fieldName && $field->hasData()) {
203                     $this->items[$i] = $newField;
204                     return true;
205                 
206                 } else if($field->isComposite()) {
207                     if($field->replaceField($fieldName, $newField)) return true;
208                 }
209             }
210         }
211         return false;
212     }
213     
214     /**
215      * Rename the title of a particular field name in this set.
216      *
217      * @param string $fieldName Name of field to rename title of
218      * @param string $newFieldTitle New title of field
219      * @return boolean
220      */
221     function renameField($fieldName, $newFieldTitle) {
222         $field = $this->dataFieldByName($fieldName);
223         if(!$field) return false;
224         
225         $field->setTitle($newFieldTitle);
226         
227         return $field->Title() == $newFieldTitle;
228     }
229     
230     /**
231      * @return boolean
232      */
233     public function hasTabSet() {
234         foreach($this->items as $i => $field) {
235             if(is_object($field) && $field instanceof TabSet) {
236                 return true;
237             }
238         }
239         
240         return false;
241     }
242     
243     /**
244      * Returns the specified tab object, creating it if necessary.
245      * 
246      * @todo Support recursive creation of TabSets
247      * 
248      * @param string $tabName The tab to return, in the form "Tab.Subtab.Subsubtab".
249      *   Caution: Does not recursively create TabSet instances, you need to make sure everything
250      *   up until the last tab in the chain exists.
251      * @param string $title Natural language title of the tab. If {@link $tabName} is passed in dot notation,
252      *   the title parameter will only apply to the innermost referenced tab.
253      *   The title is only changed if the tab doesn't exist already.
254      * @return Tab The found or newly created Tab instance
255      */
256     public function findOrMakeTab($tabName, $title = null) {
257         $parts = explode('.',$tabName);
258         
259         // We could have made this recursive, but I've chosen to keep all the logic code within FieldSet rather than add it to TabSet and Tab too.
260         $currentPointer = $this;
261         foreach($parts as $k => $part) {
262             $parentPointer = $currentPointer;
263             $currentPointer = $currentPointer->fieldByName($part);
264             // Create any missing tabs
265             if(!$currentPointer) {
266                 if(is_a($parentPointer, 'TabSet')) {
267                     // use $title on the innermost tab only
268                     if($k == count($parts)-1) {
269                         $currentPointer = ($title) ? new Tab($part, $title) : new Tab($part);
270                     } else {
271                         $currentPointer = new TabSet($part);
272                     }
273                     $parentPointer->push($currentPointer);
274                 } else {
275                     $withName = ($parentPointer->hasMethod('Name')) ? " named '{$parentPointer->Name()}'" : null;
276                     user_error("FieldSet::addFieldToTab() Tried to add a tab to object '{$parentPointer->class}'{$withName} - '$part' didn't exist.", E_USER_ERROR);
277                 }
278             }
279         }
280         
281         return $currentPointer;
282     }
283 
284     /**
285      * Returns a named field.
286      * You can use dot syntax to get fields from child composite fields
287      * 
288      * @todo Implement similiarly to dataFieldByName() to support nested sets - or merge with dataFields()
289      */
290     public function fieldByName($name) {
291         if(strpos($name,'.') !== false) list($name, $remainder) = explode('.',$name,2);
292         else $remainder = null;
293         
294         foreach($this->items as $child) {
295             if(trim($name) == trim($child->Name()) || $name == $child->id) {
296                 if($remainder) {
297                     if($child->isComposite()) {
298                         return $child->fieldByName($remainder);
299                     } else {
300                         user_error("Trying to get field '$remainder' from non-composite field $child->class.$name", E_USER_WARNING);
301                         return null;
302                     }
303                 } else {
304                     return $child;
305                 }
306             }
307         }
308     }
309     
310     /**
311      * Returns a named field in a sequential set.
312      * Use this if you're using nested FormFields.
313      * 
314      * @param string $name The name of the field to return
315      * @return FormField instance
316      */
317     public function dataFieldByName($name) {
318         if($dataFields = $this->dataFields()) {
319             foreach($dataFields as $child) {
320                 if(trim($name) == trim($child->Name()) || $name == $child->id) return $child;
321             }
322         }                                 
323     }
324 
325     /**
326      * Inserts a field before a particular field in a FieldSet.
327      *
328      * @param FormField $item The form field to insert
329      * @param string $name Name of the field to insert before
330      */
331     public function insertBefore($item, $name) {
332         $this->onBeforeInsert($item);
333         $item->setContainerFieldSet($this);
334         
335         $i = 0;
336         foreach($this->items as $child) {
337             if($name == $child->Name() || $name == $child->id) {
338                 array_splice($this->items, $i, 0, array($item));
339                 return $item;
340             } elseif($child->isComposite()) {
341                 $ret = $child->insertBefore($item, $name);
342                 if($ret) return $ret;
343             }
344             $i++;
345         }
346         
347         return false;
348     }
349 
350     /**
351      * Inserts a field after a particular field in a FieldSet.
352      *
353      * @param FormField $item The form field to insert
354      * @param string $name Name of the field to insert after
355      */
356     public function insertAfter($item, $name) {
357         $this->onBeforeInsert($item);
358         $item->setContainerFieldSet($this);
359         
360         $i = 0;
361         foreach($this->items as $child) {
362             if($name == $child->Name() || $name == $child->id) {
363                 array_splice($this->items, $i+1, 0, array($item));
364                 return $item;
365             } elseif($child->isComposite()) {
366                 $ret = $child->insertAfter($item, $name);
367                 if($ret) return $ret;
368             }
369             $i++;
370         }
371         
372         return false;
373     }
374     
375     /**
376      * Push a single field into this FieldSet instance.
377      *
378      * @param FormField $item The FormField to add
379      * @param string $key An option array key (field name)
380      */
381     public function push($item, $key = null) {
382         $this->onBeforeInsert($item);
383         $item->setContainerFieldSet($this);
384         return parent::push($item, $key = null);
385     }
386 
387     /**
388      * Handler method called before the FieldSet is going to be manipulated.
389      */
390     protected function onBeforeInsert($item) {
391         $this->flushFieldsCache();
392         if($item->Name()) $this->rootFieldSet()->removeByName($item->Name(), true);
393     }
394         
395     
396     /**
397      * Set the Form instance for this FieldSet.
398      *
399      * @param Form $form The form to set this FieldSet to
400      */
401     public function setForm($form) {
402         foreach($this as $field) $field->setForm($form);
403     }
404     
405     /**
406      * Load the given data into this form.
407      * 
408      * @param data An map of data to load into the FieldSet
409      */
410     public function setValues($data) {
411         foreach($this->dataFields() as $field) {
412             $fieldName = $field->Name();
413             if(isset($data[$fieldName])) $field->setValue($data[$fieldName]);
414         }
415     }
416     
417     /**
418      * Return all <input type="hidden"> fields
419      * in a form - including fields nested in {@link CompositeFields}.
420      * Useful when doing custom field layouts.
421      * 
422      * @return FieldSet
423      */
424     function HiddenFields() {
425         $hiddenFields = new HiddenFieldSet();
426         $dataFields = $this->dataFields();
427         
428         if($dataFields) foreach($dataFields as $field) {
429             if($field instanceof HiddenField) $hiddenFields->push($field);
430         }
431         
432         return $hiddenFields;
433     }
434 
435     /**
436      * Transform this FieldSet with a given tranform method,
437      * e.g. $this->transform(new ReadonlyTransformation())
438      * 
439      * @return FieldSet
440      */
441     function transform($trans) {
442         $this->flushFieldsCache();
443         $newFields = new FieldSet();
444         foreach($this as $field) {
445             $newFields->push($field->transform($trans));
446         }
447         return $newFields;
448     }
449 
450     /**
451      * Returns the root field set that this belongs to
452      */
453     function rootFieldSet() {
454         if($this->containerField) return $this->containerField->rootFieldSet();
455         else return $this;
456     }
457     
458     function setContainerField($field) {
459         $this->containerField = $field;
460     }
461     
462     /**
463      * Transforms this FieldSet instance to readonly.
464      *
465      * @return FieldSet
466      */
467     function makeReadonly() {
468         return $this->transform(new ReadonlyTransformation());
469     }
470 
471     /**
472      * Transform the named field into a readonly feld.
473      * 
474      * @param string|FormField
475      */
476     function makeFieldReadonly($field) {
477         $fieldName = ($field instanceof FormField) ? $field->Name() : $field;
478         $srcField = $this->dataFieldByName($fieldName);
479         $this->replaceField($fieldName, $srcField->performReadonlyTransformation());
480     }
481     
482     /**
483      * Change the order of fields in this FieldSet by specifying an ordered list of field names.
484      * This works well in conjunction with SilverStripe's scaffolding functions: take the scaffold, and
485      * shuffle the fields around to the order that you want.
486      * 
487      * Please note that any tabs or other dataless fields will be clobbered by this operation.
488      *
489      * @param array $fieldNames Field names can be given as an array, or just as a list of arguments.
490      */
491     function changeFieldOrder($fieldNames) {
492         // Field names can be given as an array, or just as a list of arguments.
493         if(!is_array($fieldNames)) $fieldNames = func_get_args();
494         
495         // Build a map of fields indexed by their name.  This will make the 2nd step much easier.
496         $fieldMap = array();
497         foreach($this->dataFields() as $field) $fieldMap[$field->Name()] = $field;
498         
499         // Iterate through the ordered list of names, building a new array to be put into $this->items.
500         // While we're doing this, empty out $fieldMap so that we can keep track of leftovers.
501         // Unrecognised field names are okay; just ignore them
502         $fields = array();
503         foreach($fieldNames as $fieldName) {
504             if(isset($fieldMap[$fieldName])) {
505                 $fields[] = $fieldMap[$fieldName];
506                 unset($fieldMap[$fieldName]);
507             }
508         }
509         
510         // Add the leftover fields to the end of the list.
511         $fields = $fields + array_values($fieldMap);
512         
513         // Update our internal $this->items parameter.
514         $this->items = $fields;
515         
516         $this->flushFieldsCache();
517     }
518     
519     /**
520      * Find the numerical position of a field within
521      * the children collection. Doesn't work recursively.
522      * 
523      * @param string|FormField
524      * @return Position in children collection (first position starts with 0). Returns FALSE if the field can't be found.
525      */
526     function fieldPosition($field) {
527         if(is_object($field)) $field = $field->Name();
528         
529         $i = 0;
530         foreach($this->dataFields() as $child) {
531             if($child->Name() == $field) return $i;
532             $i++;
533         }
534         
535         return false;
536     }
537     
538 }
539 
540 /**
541  * A fieldset designed to store a list of hidden fields.  When inserted into a template, only the
542  * input tags will be included
543  * 
544  * @package forms
545  * @subpackage fields-structural
546  */
547 class HiddenFieldSet extends FieldSet {
548     function forTemplate() {
549         $output = "";
550         foreach($this as $field) {
551             $output .= $field->Field();
552         }
553         return $output;
554     }
555 }
556 
557 ?>
558 
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0