2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Forms;
|
|
|
|
|
|
|
|
use SilverStripe\Core\Convert;
|
|
|
|
use SilverStripe\Control\Controller;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\ArrayList;
|
2017-07-21 00:20:17 +02:00
|
|
|
use SilverStripe\ORM\DataList;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\DataObject;
|
|
|
|
use SilverStripe\ORM\DataObjectInterface;
|
2016-06-23 01:37:22 +02:00
|
|
|
use SilverStripe\ORM\FieldType\DBHTMLText;
|
2017-07-21 00:20:17 +02:00
|
|
|
use SilverStripe\Security\Group;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\View\ViewableData;
|
|
|
|
use stdClass;
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* This formfield represents many-many joins using a tree selector shown in a dropdown styled element
|
2010-07-11 12:44:59 +02:00
|
|
|
* which can be added to any form usually in the CMS.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2012-09-26 23:34:00 +02:00
|
|
|
* This form class allows you to represent Many-Many Joins in a handy single field. The field has javascript which
|
|
|
|
* generates a AJAX tree of the site structure allowing you to save selected options to a component set on a given
|
|
|
|
* {@link DataObject}.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-07-11 12:44:59 +02:00
|
|
|
* <b>Saving</b>
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2012-09-26 23:34:00 +02:00
|
|
|
* This field saves a {@link ComponentSet} object which is present on the {@link DataObject} passed by the form,
|
|
|
|
* returned by calling a function with the same name as the field. The Join is updated by running setByIDList on the
|
|
|
|
* {@link ComponentSet}
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-07-11 12:44:59 +02:00
|
|
|
* <b>Customizing Save Behaviour</b>
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2012-09-26 23:34:00 +02:00
|
|
|
* Before the data is saved, you can modify the ID list sent to the {@link ComponentSet} by specifying a function on
|
|
|
|
* the {@link DataObject} called "onChange[fieldname](&items)". This will be passed by reference the IDlist (an array
|
|
|
|
* of ID's) from the Treefield to be saved to the component set.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2012-09-26 23:34:00 +02:00
|
|
|
* Returning false on this method will prevent treemultiselect from saving to the {@link ComponentSet} of the given
|
|
|
|
* {@link DataObject}
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-07-11 12:44:59 +02:00
|
|
|
* <code>
|
|
|
|
* // Called when we try and set the Parents() component set
|
|
|
|
* // by Tree Multiselect Field in the administration.
|
|
|
|
* function onChangeParents(&$items) {
|
|
|
|
* // This ensures this DataObject can never be a parent of itself
|
2016-11-29 00:31:16 +01:00
|
|
|
* if($items){
|
|
|
|
* foreach($items as $k => $id){
|
|
|
|
* if($id == $this->ID){
|
|
|
|
* unset($items[$k]);
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* return true;
|
2010-07-11 12:44:59 +02:00
|
|
|
* }
|
2014-08-15 08:53:05 +02:00
|
|
|
* </code>
|
|
|
|
*
|
2010-07-23 01:18:40 +02:00
|
|
|
* @see TreeDropdownField for the sample implementation, but only allowing single selects
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class TreeMultiselectField extends TreeDropdownField
|
|
|
|
{
|
2017-07-21 00:20:17 +02:00
|
|
|
public function __construct(
|
|
|
|
$name,
|
|
|
|
$title = null,
|
|
|
|
$sourceObject = Group::class,
|
|
|
|
$keyField = "ID",
|
|
|
|
$labelField = "Title"
|
|
|
|
) {
|
2016-11-29 00:31:16 +01:00
|
|
|
parent::__construct($name, $title, $sourceObject, $keyField, $labelField);
|
|
|
|
$this->removeExtraClass('single');
|
|
|
|
$this->addExtraClass('multiple');
|
|
|
|
$this->value = 'unchanged';
|
|
|
|
}
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
public function getSchemaDataDefaults()
|
|
|
|
{
|
|
|
|
$data = parent::getSchemaDataDefaults();
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
$data['data'] = array_merge($data['data'], [
|
|
|
|
'hasEmptyDefault' => false,
|
|
|
|
'multiple' => true,
|
|
|
|
]);
|
|
|
|
return $data;
|
|
|
|
}
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
public function getSchemaStateDefaults()
|
|
|
|
{
|
|
|
|
$data = parent::getSchemaStateDefaults();
|
|
|
|
unset($data['data']['valueObject']);
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
$items = $this->getItems();
|
|
|
|
$values = [];
|
|
|
|
foreach ($items as $item) {
|
|
|
|
if ($item instanceof DataObject) {
|
|
|
|
$values[] = [
|
2017-07-26 08:13:56 +02:00
|
|
|
'id' => $item->obj($this->getKeyField())->getValue(),
|
|
|
|
'title' => $item->obj($this->getTitleField())->getValue(),
|
2017-07-21 00:20:17 +02:00
|
|
|
'parentid' => $item->ParentID,
|
2017-07-26 08:13:56 +02:00
|
|
|
'treetitle' => $item->obj($this->getLabelField())->getSchemaValue(),
|
2017-07-21 00:20:17 +02:00
|
|
|
];
|
|
|
|
} else {
|
|
|
|
$values[] = $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$data['data']['valueObjects'] = $values;
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
// cannot rely on $this->value as this could be a many-many relationship
|
|
|
|
$value = array_column($values, 'id');
|
|
|
|
$data['value'] = ($value) ? $value : 'unchanged';
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
return $data;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return this field's linked items
|
2017-07-21 00:20:17 +02:00
|
|
|
* @return ArrayList|DataList $items
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
public function getItems()
|
|
|
|
{
|
2017-07-21 00:20:17 +02:00
|
|
|
$items = new ArrayList();
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
// If the value has been set, use that
|
2017-07-21 00:20:17 +02:00
|
|
|
if ($this->value != 'unchanged') {
|
2017-07-26 08:13:56 +02:00
|
|
|
$sourceObject = $this->getSourceObject();
|
|
|
|
if (is_array($sourceObject)) {
|
2017-07-21 00:20:17 +02:00
|
|
|
$values = is_array($this->value) ? $this->value : preg_split('/ *, */', trim($this->value));
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
foreach ($values as $value) {
|
|
|
|
$item = new stdClass;
|
|
|
|
$item->ID = $value;
|
2017-07-26 08:13:56 +02:00
|
|
|
$item->Title = $sourceObject[$value];
|
2016-11-29 00:31:16 +01:00
|
|
|
$items->push($item);
|
|
|
|
}
|
2017-07-21 00:20:17 +02:00
|
|
|
return $items;
|
|
|
|
}
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
// Otherwise, look data up from the linked relation
|
|
|
|
if (is_string($this->value)) {
|
|
|
|
$ids = explode(',', $this->value);
|
|
|
|
foreach ($ids as $id) {
|
|
|
|
if (!is_numeric($id)) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-07-26 08:13:56 +02:00
|
|
|
$item = DataObject::get_by_id($sourceObject, $id);
|
2017-07-21 00:20:17 +02:00
|
|
|
if ($item) {
|
|
|
|
$items->push($item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $items;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
2017-07-21 00:20:17 +02:00
|
|
|
}
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
if ($this->form) {
|
2016-11-29 00:31:16 +01:00
|
|
|
$fieldName = $this->name;
|
|
|
|
$record = $this->form->getRecord();
|
|
|
|
if (is_object($record) && $record->hasMethod($fieldName)) {
|
|
|
|
return $record->$fieldName();
|
|
|
|
}
|
|
|
|
}
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-07-21 00:20:17 +02:00
|
|
|
return $items;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We overwrite the field attribute to add our hidden fields, as this
|
|
|
|
* formfield can contain multiple values.
|
|
|
|
*
|
|
|
|
* @param array $properties
|
|
|
|
* @return DBHTMLText
|
|
|
|
*/
|
|
|
|
public function Field($properties = array())
|
|
|
|
{
|
|
|
|
$value = '';
|
|
|
|
$titleArray = array();
|
|
|
|
$idArray = array();
|
|
|
|
$items = $this->getItems();
|
2017-04-20 03:15:24 +02:00
|
|
|
$emptyTitle = _t('SilverStripe\\Forms\\DropdownField.CHOOSE', '(Choose)', 'start value of a dropdown');
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
if ($items && count($items)) {
|
|
|
|
foreach ($items as $item) {
|
|
|
|
$idArray[] = $item->ID;
|
|
|
|
$titleArray[] = ($item instanceof ViewableData)
|
2017-07-26 08:13:56 +02:00
|
|
|
? $item->obj($this->getLabelField())->forTemplate()
|
|
|
|
: Convert::raw2xml($item->{$this->getLabelField()});
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$title = implode(", ", $titleArray);
|
|
|
|
$value = implode(",", $idArray);
|
|
|
|
} else {
|
|
|
|
$title = $emptyTitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
$dataUrlTree = '';
|
|
|
|
if ($this->form) {
|
|
|
|
$dataUrlTree = $this->Link('tree');
|
|
|
|
if (!empty($idArray)) {
|
2018-01-16 19:39:30 +01:00
|
|
|
$dataUrlTree = Controller::join_links($dataUrlTree, '?forceValue=' . implode(',', $idArray));
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$properties = array_merge(
|
|
|
|
$properties,
|
|
|
|
array(
|
|
|
|
'Title' => $title,
|
|
|
|
'EmptyTitle' => $emptyTitle,
|
|
|
|
'Link' => $dataUrlTree,
|
|
|
|
'Value' => $value
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return FormField::Field($properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the results into the form
|
|
|
|
* Calls function $record->onChange($items) before saving to the assummed
|
|
|
|
* Component set.
|
|
|
|
*
|
|
|
|
* @param DataObjectInterface $record
|
|
|
|
*/
|
|
|
|
public function saveInto(DataObjectInterface $record)
|
|
|
|
{
|
2017-10-05 03:58:27 +02:00
|
|
|
$items = [];
|
|
|
|
$fieldName = $this->name;
|
|
|
|
$saveDest = $record->$fieldName();
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-10-05 03:58:27 +02:00
|
|
|
if (!$saveDest) {
|
|
|
|
$recordClass = get_class($record);
|
|
|
|
user_error(
|
|
|
|
"TreeMultiselectField::saveInto() Field '$fieldName' not found on"
|
|
|
|
. " {$recordClass}.{$record->ID}",
|
|
|
|
E_USER_ERROR
|
|
|
|
);
|
|
|
|
}
|
2017-07-26 08:13:56 +02:00
|
|
|
|
2017-10-05 03:58:27 +02:00
|
|
|
// Detect whether this field has actually been updated
|
|
|
|
if ($this->value !== 'unchanged') {
|
2017-07-21 00:20:17 +02:00
|
|
|
if (is_array($this->value)) {
|
|
|
|
$items = $this->value;
|
|
|
|
} elseif ($this->value) {
|
2016-11-29 00:31:16 +01:00
|
|
|
$items = preg_split("/ *, */", trim($this->value));
|
|
|
|
}
|
2017-10-05 03:58:27 +02:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-10-05 03:58:27 +02:00
|
|
|
// Allows you to modify the items on your object before save
|
|
|
|
$funcName = "onChange$fieldName";
|
|
|
|
if ($record->hasMethod($funcName)) {
|
|
|
|
$result = $record->$funcName($items);
|
|
|
|
if (!$result) {
|
|
|
|
return;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
}
|
2017-10-05 03:58:27 +02:00
|
|
|
$saveDest->setByIDList($items);
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes this field to the readonly field.
|
|
|
|
*/
|
|
|
|
public function performReadonlyTransformation()
|
|
|
|
{
|
2017-07-26 08:13:56 +02:00
|
|
|
/** @var TreeMultiselectField_Readonly $copy */
|
|
|
|
$copy = $this->castedCopy(TreeMultiselectField_Readonly::class);
|
|
|
|
$copy->setKeyField($this->getKeyField());
|
|
|
|
$copy->setLabelField($this->getLabelField());
|
|
|
|
$copy->setSourceObject($this->getSourceObject());
|
|
|
|
$copy->setTitleField($this->getTitleField());
|
2016-11-29 00:31:16 +01:00
|
|
|
return $copy;
|
|
|
|
}
|
2009-06-17 13:36:49 +02:00
|
|
|
}
|