mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
3ee8f505b7
The main benefit of this is so that authors who make use of .editorconfig don't end up with whitespace changes in their PRs. Spaces vs. tabs has been left alone, although that could do with a tidy-up in SS4 after the switch to PSR-1/2. The command used was this: for match in '*.ss' '*.css' '*.scss' '*.html' '*.yml' '*.php' '*.js' '*.csv' '*.inc' '*.php5'; do find . -path ./thirdparty -not -prune -o -path ./admin/thirdparty -not -prune -o -type f -name "$match" -exec sed -E -i '' 's/[[:space:]]+$//' {} \+ find . -path ./thirdparty -not -prune -o -path ./admin/thirdparty -not -prune -o -type f -name "$match" | xargs perl -pi -e 's/ +$//' done
398 lines
9.5 KiB
PHP
398 lines
9.5 KiB
PHP
<?php
|
|
/**
|
|
* Base class for all fields that contain other fields.
|
|
*
|
|
* Implements sequentialisation - so that when we're saving / loading data, we
|
|
* can populate a tabbed form properly. All of the children are stored in
|
|
* $this->children
|
|
*
|
|
* @package forms
|
|
* @subpackage fields-structural
|
|
*/
|
|
class CompositeField extends FormField {
|
|
|
|
/**
|
|
* @var FieldList
|
|
*/
|
|
protected $children;
|
|
|
|
/**
|
|
* Set to true when this field is a readonly field
|
|
*/
|
|
protected $readonly;
|
|
|
|
/**
|
|
* @var $columnCount int Toggle different css-rendering for multiple columns
|
|
* ("onecolumn", "twocolumns", "threecolumns"). The content is determined
|
|
* by the $children-array, so wrap all items you want to have grouped in a
|
|
* column inside a CompositeField.
|
|
* Caution: Please make sure that this variable actually matches the
|
|
* count of your $children.
|
|
*/
|
|
protected $columnCount = null;
|
|
|
|
/**
|
|
* @var String custom HTML tag to render with, e.g. to produce a <fieldset>.
|
|
*/
|
|
protected $tag = 'div';
|
|
|
|
/**
|
|
* @var String Optional description for this set of fields.
|
|
* If the {@link $tag} property is set to use a 'fieldset', this will be
|
|
* rendered as a <legend> tag, otherwise its a 'title' attribute.
|
|
*/
|
|
protected $legend;
|
|
|
|
public function __construct($children = null) {
|
|
if($children instanceof FieldList) {
|
|
$this->children = $children;
|
|
} elseif(is_array($children)) {
|
|
$this->children = new FieldList($children);
|
|
} else {
|
|
//filter out null/empty items
|
|
$children = array_filter(func_get_args());
|
|
$this->children = new FieldList($children);
|
|
}
|
|
$this->children->setContainerField($this);
|
|
|
|
// Skipping FormField::__construct(), but we have to make sure this
|
|
// doesn't count as a broken constructor
|
|
$this->brokenOnConstruct = false;
|
|
Object::__construct();
|
|
}
|
|
|
|
/**
|
|
* Returns all the sub-fields, suitable for <% loop FieldList %>
|
|
*
|
|
* @return FieldList
|
|
*/
|
|
public function FieldList() {
|
|
return $this->children;
|
|
}
|
|
|
|
public function setID($id) {
|
|
$this->id = $id;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Accessor method for $this->children
|
|
*
|
|
* @return FieldList
|
|
*/
|
|
public function getChildren() {
|
|
return $this->children;
|
|
}
|
|
|
|
/**
|
|
* @param FieldList $children
|
|
*/
|
|
public function setChildren($children) {
|
|
$this->children = $children;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param string
|
|
*/
|
|
public function setTag($tag) {
|
|
$this->tag = $tag;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getTag() {
|
|
return $this->tag;
|
|
}
|
|
|
|
/**
|
|
* @param string
|
|
*/
|
|
public function setLegend($legend) {
|
|
$this->legend = $legend;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getLegend() {
|
|
return $this->legend;
|
|
}
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
public function extraClasses() {
|
|
Deprecation::notice('4.0', 'Use extraClass() instead');
|
|
return $this->extraClass();
|
|
}
|
|
|
|
public function extraClass() {
|
|
$classes = array('field', 'CompositeField', parent::extraClass());
|
|
if($this->columnCount) $classes[] = 'multicolumn';
|
|
|
|
return implode(' ', $classes);
|
|
}
|
|
|
|
public function getAttributes() {
|
|
return array_merge(
|
|
parent::getAttributes(),
|
|
array(
|
|
'tabindex' => null,
|
|
'type' => null,
|
|
'value' => null,
|
|
'type' => null,
|
|
'title' => ($this->tag == 'fieldset') ? null : $this->legend
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add all of the non-composite fields contained within this field to the
|
|
* list.
|
|
*
|
|
* Sequentialisation is used when connecting the form to its data source
|
|
*/
|
|
public function collateDataFields(&$list, $saveableOnly = false) {
|
|
foreach($this->children as $field) {
|
|
if(is_object($field)) {
|
|
if($field->isComposite()) $field->collateDataFields($list, $saveableOnly);
|
|
if($saveableOnly) {
|
|
$isIncluded = ($field->hasData() && !$field->isReadonly() && !$field->isDisabled());
|
|
} else {
|
|
$isIncluded = ($field->hasData());
|
|
}
|
|
if($isIncluded) {
|
|
$name = $field->getName();
|
|
if($name) {
|
|
$formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)';
|
|
if(isset($list[$name])) {
|
|
user_error("collateDataFields() I noticed that a field called '$name' appears twice in"
|
|
. " your form: '{$formName}'. One is a '{$field->class}' and the other is a"
|
|
. " '{$list[$name]->class}'", E_USER_ERROR);
|
|
}
|
|
$list[$name] = $field;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function setForm($form) {
|
|
foreach($this->children as $f)
|
|
if(is_object($f)) $f->setForm($form);
|
|
|
|
parent::setForm($form);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setColumnCount($columnCount) {
|
|
$this->columnCount = $columnCount;
|
|
return $this;
|
|
}
|
|
|
|
public function getColumnCount() {
|
|
return $this->columnCount;
|
|
}
|
|
|
|
public function isComposite() {
|
|
return true;
|
|
}
|
|
|
|
public function hasData() {
|
|
return false;
|
|
}
|
|
|
|
public function fieldByName($name) {
|
|
return $this->children->fieldByName($name);
|
|
}
|
|
|
|
/**
|
|
* Add a new child field to the end of the set.
|
|
*
|
|
* @param FormField
|
|
*/
|
|
public function push(FormField $field) {
|
|
$this->children->push($field);
|
|
}
|
|
|
|
/**
|
|
* Add a new child field to the beginning of the set.
|
|
*
|
|
* @param FormField
|
|
*/
|
|
public function unshift(FormField $field) {
|
|
$this->children->unshift($field);
|
|
}
|
|
|
|
/**
|
|
* @uses FieldList->insertBefore()
|
|
*/
|
|
public function insertBefore($insertBefore, $field) {
|
|
$ret = $this->children->insertBefore($insertBefore, $field);
|
|
$this->sequentialSet = null;
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* @uses FieldList->insertAfter()
|
|
*/
|
|
public function insertAfter($insertAfter, $field) {
|
|
$ret = $this->children->insertAfter($insertAfter, $field);
|
|
$this->sequentialSet = null;
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Remove a field from this CompositeField by Name.
|
|
* The field could also be inside a CompositeField.
|
|
*
|
|
* @param string $fieldName The name of the field
|
|
* @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.
|
|
*/
|
|
public function removeByName($fieldName, $dataFieldOnly = false) {
|
|
$this->children->removeByName($fieldName, $dataFieldOnly);
|
|
}
|
|
|
|
public function replaceField($fieldName, $newField) {
|
|
return $this->children->replaceField($fieldName, $newField);
|
|
}
|
|
|
|
public function rootFieldList() {
|
|
if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList();
|
|
else return $this->children;
|
|
}
|
|
|
|
/**
|
|
* Return a readonly version of this field. Keeps the composition but returns readonly
|
|
* versions of all the child {@link FormField} objects.
|
|
*
|
|
* @return CompositeField
|
|
*/
|
|
public function performReadonlyTransformation() {
|
|
$newChildren = new FieldList();
|
|
$clone = clone $this;
|
|
if($clone->getChildren()) foreach($clone->getChildren() as $idx => $child) {
|
|
if(is_object($child)) $child = $child->transform(new ReadonlyTransformation());
|
|
$newChildren->push($child, $idx);
|
|
}
|
|
|
|
$clone->children = $newChildren;
|
|
$clone->readonly = true;
|
|
$clone->addExtraClass($this->extraClass());
|
|
$clone->setDescription($this->getDescription());
|
|
|
|
return $clone;
|
|
}
|
|
|
|
/**
|
|
* Return a disabled version of this field. Keeps the composition but returns disabled
|
|
* versions of all the child {@link FormField} objects.
|
|
*
|
|
* @return CompositeField
|
|
*/
|
|
public function performDisabledTransformation() {
|
|
$newChildren = new FieldList();
|
|
$clone = clone $this;
|
|
if($clone->getChildren()) foreach($clone->getChildren() as $idx => $child) {
|
|
if(is_object($child)) $child = $child->transform(new DisabledTransformation());
|
|
$newChildren->push($child, $idx);
|
|
}
|
|
|
|
$clone->children = $newChildren;
|
|
$clone->readonly = true;
|
|
$clone->addExtraClass($this->extraClass());
|
|
$clone->setDescription($this->getDescription());
|
|
foreach($this->attributes as $k => $v) {
|
|
$clone->setAttribute($k, $v);
|
|
}
|
|
|
|
return $clone;
|
|
}
|
|
|
|
public function IsReadonly() {
|
|
return $this->readonly;
|
|
}
|
|
|
|
/**
|
|
* Find the numerical position of a field within
|
|
* the children collection. Doesn't work recursively.
|
|
*
|
|
* @param string|FormField
|
|
* @return int Position in children collection (first position starts with 0). Returns FALSE if the field can't
|
|
* be found.
|
|
*/
|
|
public function fieldPosition($field) {
|
|
if(is_string($field)) $field = $this->fieldByName($field);
|
|
if(!$field) return false;
|
|
|
|
$i = 0;
|
|
foreach($this->children as $child) {
|
|
if($child->getName() == $field->getName()) return $i;
|
|
$i++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Transform the named field into a readonly feld.
|
|
*
|
|
* @param string|FormField
|
|
*/
|
|
public function makeFieldReadonly($field) {
|
|
$fieldName = ($field instanceof FormField) ? $field->getName() : $field;
|
|
|
|
// Iterate on items, looking for the applicable field
|
|
foreach($this->children as $i => $item) {
|
|
if($item->isComposite()) {
|
|
$item->makeFieldReadonly($fieldName);
|
|
} else {
|
|
// Once it's found, use FormField::transform to turn the field into a readonly version of itself.
|
|
if($item->getName() == $fieldName) {
|
|
$this->children->replaceField($fieldName, $item->transform(new ReadonlyTransformation()));
|
|
|
|
// Clear an internal cache
|
|
$this->sequentialSet = null;
|
|
|
|
// A true results indicates that the field was found
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function debug() {
|
|
$result = "$this->class ($this->name) <ul>";
|
|
foreach($this->children as $child) {
|
|
$result .= "<li>" . Debug::text($child) . " </li>";
|
|
}
|
|
$result .= "</ul>";
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Validate this field
|
|
*
|
|
* @param Validator $validator
|
|
* @return bool
|
|
*/
|
|
public function validate($validator) {
|
|
$valid = true;
|
|
foreach($this->children as $idx => $child){
|
|
$valid = ($child && $child->validate($validator) && $valid);
|
|
}
|
|
return $valid;
|
|
}
|
|
|
|
}
|
|
|