2007-07-19 12:40:28 +02:00
< ? php
/**
* DataObjectSet designed for form fields .
* It extends the DataObjectSet with the ability to get a sequential set of fields .
2008-01-09 05:18:36 +01:00
* @ package forms
* @ subpackage fields - structural
2007-07-19 12:40:28 +02:00
*/
class FieldSet extends DataObjectSet {
2008-03-16 23:15:04 +01:00
/**
* Cached flat representation of all fields in this set ,
* including fields nested in { @ link CompositeFields } .
*
* @ uses self :: collateDataFields ()
* @ var array
*/
2007-07-19 12:40:28 +02:00
protected $sequentialSet ;
2008-10-03 17:55:39 +02:00
/**
* @ var array
*/
2008-08-12 04:58:48 +02:00
protected $sequentialSaveableSet ;
2007-07-19 12:40:28 +02:00
2008-10-03 17:55:39 +02:00
/**
* @ todo Documentation
*/
protected $containerField ;
2008-09-11 02:02:49 +02:00
public function __construct ( $items = null ) {
// if the first parameter is not an array, or we have more than one parameter, collate all parameters to an array
// otherwise use the passed array
$itemsArr = ( ! is_array ( $items ) || count ( func_get_args ()) > 1 ) ? func_get_args () : $items ;
parent :: __construct ( $itemsArr );
foreach ( $this -> items as $item ) {
$item -> setContainerFieldSet ( $this );
}
}
2007-07-19 12:40:28 +02:00
/**
* Return a sequential set of all fields that have data . This excludes wrapper composite fields
* as well as heading / help text fields .
*/
public function dataFields () {
if ( ! $this -> sequentialSet ) $this -> collateDataFields ( $this -> sequentialSet );
return $this -> sequentialSet ;
}
2008-08-12 04:58:48 +02:00
public function saveableFields () {
if ( ! $this -> sequentialSaveableSet ) $this -> collateDataFields ( $this -> sequentialSaveableSet , true );
return $this -> sequentialSaveableSet ;
}
protected function collateDataFields ( & $list , $saveableOnly = false ) {
2007-07-19 12:40:28 +02:00
foreach ( $this as $field ) {
2008-08-12 04:58:48 +02:00
if ( $field -> isComposite ()) $field -> collateDataFields ( $list , $saveableOnly );
2007-11-23 02:10:19 +01:00
2008-08-12 04:58:48 +02:00
if ( $saveableOnly ) {
$isIncluded = ( $field -> hasData () && ! $field -> isReadonly () && ! $field -> isDisabled ());
} else {
$isIncluded = ( $field -> hasData ());
}
if ( $isIncluded ) {
2007-07-19 12:40:28 +02:00
$name = $field -> Name ();
if ( isset ( $list [ $name ])) {
2008-08-09 06:38:44 +02:00
$errSuffix = " " ;
2007-07-19 12:40:28 +02:00
if ( $this -> form ) $errSuffix = " in your ' { $this -> form -> class } ' form called ' " . $this -> form -> Name () . " ' " ;
2008-04-26 08:54:47 +02:00
else $errSuffix = '' ;
2007-07-19 12:40:28 +02:00
user_error ( " collateDataFields() I noticed that a field called ' $name ' appears twice $errSuffix . " , E_USER_ERROR );
}
$list [ $name ] = $field ;
}
}
}
/**
* Add an extra field to a tab within this fieldset .
* This is most commonly used when overloading getCMSFields ()
2008-09-11 02:02:49 +02:00
*
* @ param string $tabName The name of the tab or tabset . Subtabs can be referred to as TabSet . Tab or TabSet . Tab . Subtab .
2007-07-19 12:40:28 +02:00
* This function will create any missing tabs .
2008-09-11 02:02:49 +02:00
* @ param FormField $field The { @ link FormField } object to add to the end of that tab .
* @ param string $insertBefore The name of the field to insert before . Optional .
2007-07-19 12:40:28 +02:00
*/
public function addFieldToTab ( $tabName , $field , $insertBefore = null ) {
// This is a cache that must be flushed
$this -> sequentialSet = null ;
// Find the tab
$tab = $this -> findOrMakeTab ( $tabName );
2008-09-11 02:02:49 +02:00
2007-07-19 12:40:28 +02:00
// Add the field to the end of this set
if ( $insertBefore ) $tab -> insertBefore ( $field , $insertBefore );
else $tab -> push ( $field );
}
2008-09-11 02:02:49 +02:00
/**
* Add a number of extra fields to a tab within this fieldset .
* This is most commonly used when overloading getCMSFields ()
*
* @ param string $tabName The name of the tab or tabset . Subtabs can be referred to as TabSet . Tab or TabSet . Tab . Subtab .
* This function will create any missing tabs .
* @ param array $fields An array of { @ link FormField } objects .
*/
public function addFieldsToTab ( $tabName , $fields ) {
$this -> sequentialSet = null ;
// Find the tab
$tab = $this -> findOrMakeTab ( $tabName );
// Add the fields to the end of this set
foreach ( $fields as $field ) {
// Check if a field by the same name exists in this tab
if ( $tab -> fieldByName ( $field -> Name ())) {
// It exists, so we need to replace the old one
$this -> replaceField ( $field -> Name (), $field );
} else {
$tab -> push ( $field );
}
}
}
2007-07-19 12:40:28 +02:00
/**
* Remove the given field from the given tab in the field .
2008-09-11 02:02:49 +02:00
*
* @ param string $tabName The name of the tab
* @ param string $fieldName The name of the field
2007-07-19 12:40:28 +02:00
*/
public function removeFieldFromTab ( $tabName , $fieldName ) {
// This is a cache that must be flushed
$this -> sequentialSet = null ;
// Find the tab
$tab = $this -> findOrMakeTab ( $tabName );
$tab -> removeByName ( $fieldName );
}
2008-08-20 07:03:50 +02:00
/**
* Removes a number of fields from a Tab / TabSet within this FieldSet .
*
* @ param string $tabName The name of the Tab or TabSet field
* @ param array $fields A list of fields , e . g . array ( 'Name' , 'Email' )
*/
public function removeFieldsFromTab ( $tabName , $fields ) {
// This is a cache that must be flushed
$this -> sequentialSet = null ;
// Find the tab
$tab = $this -> findOrMakeTab ( $tabName );
// Add the fields to the end of this set
foreach ( $fields as $field ) $tab -> removeByName ( $field );
}
2007-07-19 12:40:28 +02:00
/**
2008-09-11 02:02:49 +02:00
* Remove a field from this FieldSet by Name .
* The field could also be inside a CompositeField .
*
2008-10-03 17:55:39 +02:00
* @ param string $fieldName The name of the field or tab
2008-09-12 06:42:24 +02:00
* @ param boolean $dataFieldOnly If this is true , then a field will only
* be removed if it ' s a data field . Dataless fields , such as tabs , will
* be left as - is .
2007-07-19 12:40:28 +02:00
*/
2008-09-12 06:42:24 +02:00
public function removeByName ( $fieldName , $dataFieldOnly = false ) {
2007-07-19 12:40:28 +02:00
foreach ( $this -> items as $i => $child ) {
2008-09-12 06:42:24 +02:00
if ( is_object ( $child ) && ( $child -> Name () == $fieldName || $child -> Title () == $fieldName ) && ( ! $dataFieldOnly || $child -> hasData ())) {
2008-09-18 00:31:01 +02:00
//if($child->class == 'Tab' && !$dataFieldOnly) Debug::backtrace();
2007-07-19 12:40:28 +02:00
array_splice ( $this -> items , $i , 1 );
break ;
2008-09-12 06:42:24 +02:00
} else if ( $child -> isComposite ()) {
$child -> removeByName ( $fieldName , $dataFieldOnly );
}
2007-07-19 12:40:28 +02:00
}
}
2008-08-20 07:11:07 +02:00
/**
2008-10-08 05:37:12 +02:00
* Replace a single field with another . Ignores dataless fields such as Tabs and TabSets
2008-08-20 07:11:07 +02:00
*
* @ param string $fieldName The name of the field to replace
* @ param FormField $newField The field object to replace with
* @ return boolean TRUE field was successfully replaced
* FALSE field wasn ' t found , nothing changed
*/
2007-07-19 12:40:28 +02:00
public function replaceField ( $fieldName , $newField ) {
if ( $this -> sequentialSet ) $this -> sequentialSet = null ;
foreach ( $this -> items as $i => $field ) {
if ( is_object ( $field )) {
2008-10-08 05:37:12 +02:00
if ( $field -> Name () == $fieldName && $field -> hasData ()) {
2007-07-19 12:40:28 +02:00
$this -> items [ $i ] = $newField ;
return true ;
} else if ( $field -> isComposite ()) {
if ( $field -> replaceField ( $fieldName , $newField )) return true ;
}
}
}
return false ;
}
2008-10-03 20:29:43 +02:00
/**
* @ return boolean
*/
public function hasTabSet () {
foreach ( $this -> items as $i => $field ) {
if ( is_object ( $field ) && $field instanceof TabSet ) {
return true ;
}
}
return false ;
}
2007-07-19 12:40:28 +02:00
/**
* Returns the specified tab object , creating it if necessary .
2008-09-11 02:02:49 +02:00
*
2007-07-19 12:40:28 +02:00
* @ param tabName The tab to return , in the form " Tab.Subtab.Subsubtab "
*/
protected function findOrMakeTab ( $tabName ) {
$parts = explode ( '.' , $tabName );
2008-08-06 05:43:48 +02:00
2007-07-19 12:40:28 +02:00
// 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.
$currentPointer = $this ;
foreach ( $parts as $part ) {
$parentPointer = $currentPointer ;
$currentPointer = $currentPointer -> fieldByName ( $part );
// Create any missing tabs
if ( ! $currentPointer ) {
2008-09-11 02:02:49 +02:00
if ( is_a ( $parentPointer , 'TabSet' )) {
2008-08-09 07:57:44 +02:00
$currentPointer = new Tab ( $part );
2007-07-19 12:40:28 +02:00
$parentPointer -> push ( $currentPointer );
} else {
2008-10-08 05:37:12 +02:00
user_error ( " FieldSet::addFieldToTab() Tried to add a tab to object ' { $parentPointer -> class } . { $parentPointer -> Name () } ' - ' $part ' didn't exist. " , E_USER_ERROR );
2007-07-19 12:40:28 +02:00
}
}
}
return $currentPointer ;
}
/**
2008-06-12 10:54:03 +02:00
* Returns a named field .
2008-03-16 23:15:04 +01:00
*
* @ todo Implement similiarly to dataFieldByName () to support nested sets - or merge with dataFields ()
2007-07-19 12:40:28 +02:00
*/
public function fieldByName ( $name ) {
foreach ( $this -> items as $child ) {
if ( $name == $child -> Name () || $name == $child -> id ) return $child ;
}
}
/**
2008-06-12 10:54:03 +02:00
* Returns a named field in a sequential set .
* Use this if you ' re using nested FormFields .
*
* @ param string $name The name of the field to return
* @ return FormField instance
2007-07-19 12:40:28 +02:00
*/
public function dataFieldByName ( $name ) {
2008-06-12 10:54:03 +02:00
if ( $dataFields = $this -> dataFields ()) {
2007-07-19 12:40:28 +02:00
foreach ( $dataFields as $child ) {
if ( $name == $child -> Name () || $name == $child -> id ) return $child ;
}
}
}
2008-06-12 10:54:03 +02:00
/**
* Inserts a field before a particular field in a FieldSet .
*
* @ param FormField $item The form field to insert
* @ param string $name Name of the field to insert before
*/
2007-07-19 12:40:28 +02:00
public function insertBefore ( $item , $name ) {
2008-09-11 02:02:49 +02:00
$this -> beforeInsert ( $item );
$item -> setContainerFieldSet ( $this );
2007-07-19 12:40:28 +02:00
$i = 0 ;
foreach ( $this -> items as $child ) {
if ( $name == $child -> Name () || $name == $child -> id ) {
array_splice ( $this -> items , $i , 0 , array ( $item ));
return ;
}
$i ++ ;
}
$this -> items [] = $item ;
}
2007-09-16 18:06:52 +02:00
/**
* Inserts an item before the item with name $name
* It can be buried in a composite field
* If no item with name $name is found , $item is inserted at the end of the FieldSet
*
* @ param FormField $item The item to be inserted
* @ param string $name The name of the item that $item should be before
* @ param int $level For internal use only , should not be passed
*/
public function insertBeforeRecursive ( $item , $name , $level = 0 ) {
2008-09-11 02:02:49 +02:00
$this -> beforeInsert ( $item );
$item -> setContainerFieldSet ( $this );
2007-09-16 18:06:52 +02:00
$i = 0 ;
foreach ( $this -> items as $child ) {
if ( $name == $child -> Name () || $name == $child -> id ) {
array_splice ( $this -> items , $i , 0 , array ( $item ));
return $level ;
} else if ( $child -> isComposite ()) {
if ( $level = $child -> insertBeforeRecursive ( $item , $name , $level + 1 )) return $level ;
}
$i ++ ;
}
if ( $level === 0 ) {
$this -> items [] = $item ;
return 0 ;
}
return false ;
}
2008-06-12 10:54:03 +02:00
/**
* Inserts a field after a particular field in a FieldSet .
*
* @ param FormField $item The form field to insert
* @ param string $name Name of the field to insert after
*/
2007-07-19 12:40:28 +02:00
public function insertAfter ( $item , $name ) {
2008-09-11 02:02:49 +02:00
$this -> beforeInsert ( $item );
$item -> setContainerFieldSet ( $this );
2007-07-19 12:40:28 +02:00
$i = 0 ;
foreach ( $this -> items as $child ) {
if ( $name == $child -> Name () || $name == $child -> id ) {
array_splice ( $this -> items , $i + 1 , 0 , array ( $item ));
return ;
}
$i ++ ;
}
$this -> items [] = $item ;
}
2008-06-12 10:54:03 +02:00
/**
* Push a single field into this FieldSet instance .
*
* @ param FormField $item The FormField to add
* @ param string $key An option array key ( field name )
*/
2007-07-19 12:40:28 +02:00
public function push ( $item , $key = null ) {
2008-09-11 02:02:49 +02:00
$this -> beforeInsert ( $item );
$item -> setContainerFieldSet ( $this );
2007-07-19 12:40:28 +02:00
return parent :: push ( $item , $key = null );
}
2008-06-12 10:54:03 +02:00
2008-09-11 02:02:49 +02:00
/**
* Handler method called before the FieldSet is going to be manipulated .
*/
function beforeInsert ( $item ) {
if ( $this -> sequentialSet ) $this -> sequentialSet = null ;
2008-09-12 06:42:24 +02:00
$this -> rootFieldSet () -> removeByName ( $item -> Name (), true );
2008-09-11 02:02:49 +02:00
}
2008-06-12 10:54:03 +02:00
/**
* Set the Form instance for this FieldSet .
*
2008-09-11 02:02:49 +02:00
* @ param Form $form The form to set this FieldSet to
2008-06-12 10:54:03 +02:00
*/
2007-07-19 12:40:28 +02:00
public function setForm ( $form ) {
foreach ( $this as $field ) $field -> setForm ( $form );
}
/**
* Load the given data into this form .
2008-09-11 02:02:49 +02:00
*
* @ param data An map of data to load into the FieldSet
2007-07-19 12:40:28 +02:00
*/
public function setValues ( $data ) {
foreach ( $this -> dataFields () as $field ) {
$fieldName = $field -> Name ();
if ( isset ( $data [ $fieldName ])) $field -> setValue ( $data [ $fieldName ]);
}
}
2008-03-16 23:15:04 +01:00
/**
* Return all < input type = " hidden " > fields
* in a form - including fields nested in { @ link CompositeFields } .
* Useful when doing custom field layouts .
*
* @ return FieldSet
*/
function HiddenFields () {
$hiddenFields = new FieldSet ();
$dataFields = $this -> dataFields ();
if ( $dataFields ) foreach ( $dataFields as $field ) {
if ( $field instanceof HiddenField ) $hiddenFields -> push ( $field );
}
return $hiddenFields ;
}
2007-07-19 12:40:28 +02:00
/**
2008-06-12 10:54:03 +02:00
* Transform this FieldSet with a given tranform method ,
* e . g . $this -> transform ( new ReadonlyTransformation ())
*
* @ return FieldSet
2007-07-19 12:40:28 +02:00
*/
function transform ( $trans ) {
$this -> sequentialSet = null ;
$newFields = new FieldSet ();
foreach ( $this as $field ) {
$newFields -> push ( $field -> transform ( $trans ));
}
return $newFields ;
}
2008-09-11 02:02:49 +02:00
/**
* Returns the root field set that this belongs to
*/
function rootFieldSet () {
if ( $this -> containerField ) return $this -> containerField -> rootFieldSet ();
else return $this ;
}
function setContainerField ( $field ) {
$this -> containerField = $field ;
}
2007-07-19 12:40:28 +02:00
2008-06-12 10:54:03 +02:00
/**
* Transforms this FieldSet instance to readonly .
*
* @ return FieldSet
*/
2007-07-19 12:40:28 +02:00
function makeReadonly () {
return $this -> transform ( new ReadonlyTransformation ());
}
2008-08-11 01:29:30 +02:00
/**
* Transform the named field into a readonly feld .
*/
function makeFieldReadonly ( $fieldName ) {
// Iterate on items, looking for the applicable field
foreach ( $this -> items as $i => $field ) {
// Once it's found, use FormField::transform to turn the field into a readonly version of itself.
if ( $field -> Name () == $fieldName ) {
$this -> items [ $i ] = $field -> transform ( new ReadonlyTransformation ());
// Clear an internal cache
$this -> sequentialSet = null ;
// A true results indicates that the field was foudn
return true ;
}
}
return false ;
}
/**
* Change the order of fields in this FieldSet by specifying an ordered list of field names .
* This works well in conjunction with SilverStripe ' s scaffolding functions : take the scaffold , and
* shuffle the fields around to the order that you want .
*
* Please note that any tabs or other dataless fields will be clobbered by this operation .
*
* Field names can be given as an array , or just as a list of arguments .
*/
function changeFieldOrder ( $fieldNames ) {
// Field names can be given as an array, or just as a list of arguments.
if ( ! is_array ( $fieldNames )) $fieldNames = func_get_args ();
// Build a map of fields indexed by their name. This will make the 2nd step much easier.
$fieldMap = array ();
foreach ( $this -> dataFields () as $field ) $fieldMap [ $field -> Name ()] = $field ;
// Iterate through the ordered list of names, building a new array to be put into $this->items.
// While we're doing this, empty out $fieldMap so that we can keep track of leftovers.
// Unrecognised field names are okay; just ignore them
$fields = array ();
foreach ( $fieldNames as $fieldName ) {
if ( isset ( $fieldMap [ $fieldName ])) {
$fields [] = $fieldMap [ $fieldName ];
unset ( $fieldMap [ $fieldName ]);
}
}
// Add the leftover fields to the end of the list.
$fields = $fields + array_values ( $fieldMap );
// Update our internal $this->items parameter.
$this -> items = $fields ;
// Re-set an internal cache
$this -> sequentialSet = null ;
}
2007-07-19 12:40:28 +02:00
}
2008-03-16 23:15:04 +01:00
?>