2011-12-06 01:56:24 +01:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Displays a {@link SS_List} in a grid format.
|
|
|
|
*
|
|
|
|
* GridField is a field that takes an SS_List and displays it in an table with rows
|
|
|
|
* and columns. It reminds of the old TableFields but works with SS_List types
|
|
|
|
* and only loads the necessary rows from the list.
|
|
|
|
*
|
|
|
|
* The minimum configuration is to pass in name and title of the field and a
|
|
|
|
* SS_List.
|
|
|
|
*
|
|
|
|
* <code>
|
|
|
|
* $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page'));
|
|
|
|
* </code>
|
|
|
|
*
|
|
|
|
* @see SS_List
|
|
|
|
*
|
2012-04-12 08:02:46 +02:00
|
|
|
* @package framework
|
2011-12-06 01:56:24 +01:00
|
|
|
* @subpackage fields-relational
|
|
|
|
*/
|
|
|
|
class GridField extends FormField {
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
public static $allowed_actions = array(
|
2012-02-09 11:16:29 +01:00
|
|
|
'index',
|
2011-12-06 01:56:24 +01:00
|
|
|
'gridFieldAlterAction'
|
|
|
|
);
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
/**
|
|
|
|
* The datasource
|
|
|
|
* @var SS_List
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
protected $list = null;
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
/**
|
|
|
|
* The classname of the DataObject that the GridField will display. Defaults to the value of $this->list->dataClass
|
|
|
|
* @var string
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
protected $modelClassName = '';
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
/**
|
|
|
|
* the current state of the GridField
|
|
|
|
* @var GridState
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
protected $state = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @var GridFieldConfig
|
|
|
|
*/
|
|
|
|
protected $config = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The components list
|
|
|
|
*/
|
|
|
|
protected $components = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dispatcher for column handlers.
|
|
|
|
* Keys are column names and values are GridField_ColumnProvider objects
|
2012-01-07 05:18:00 +01:00
|
|
|
*
|
|
|
|
* @var array
|
2011-12-06 01:56:24 +01:00
|
|
|
*/
|
|
|
|
protected $columnDispatch = null;
|
2012-05-29 00:06:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Map of callbacks for custom data fields
|
|
|
|
*/
|
|
|
|
protected $customDataFields = array();
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-05-21 07:47:14 +02:00
|
|
|
protected $name = '';
|
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
/**
|
|
|
|
* Creates a new GridField field
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
* @param string $title
|
|
|
|
* @param SS_List $dataList
|
|
|
|
* @param GridFieldConfig $config
|
|
|
|
*/
|
|
|
|
public function __construct($name, $title = null, SS_List $dataList = null, GridFieldConfig $config = null) {
|
|
|
|
parent::__construct($name, $title, null);
|
2012-05-21 07:47:14 +02:00
|
|
|
$this->name = $name;
|
2011-12-06 01:56:24 +01:00
|
|
|
|
|
|
|
if($dataList) {
|
|
|
|
$this->setList($dataList);
|
|
|
|
}
|
2012-07-04 05:03:13 +02:00
|
|
|
|
|
|
|
$this->setConfig($config ?: GridFieldConfig_Base::create());
|
|
|
|
|
2012-01-07 04:48:49 +01:00
|
|
|
$this->config->addComponent(new GridState_Component());
|
2012-05-21 07:47:14 +02:00
|
|
|
$this->state = new GridState($this);
|
2011-12-06 01:56:24 +01:00
|
|
|
|
|
|
|
$this->addExtraClass('ss-gridfield');
|
2012-02-09 11:16:29 +01:00
|
|
|
}
|
|
|
|
|
2012-09-19 12:07:39 +02:00
|
|
|
public function index($request) {
|
2012-02-09 11:16:29 +01:00
|
|
|
return $this->gridFieldAlterAction(array(), $this->getForm(), $request);
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-01-07 05:18:00 +01:00
|
|
|
* Set the modelClass (dataobject) that this field will get it column headers from.
|
|
|
|
* If no $displayFields has been set, the displayfields will be fetched from
|
|
|
|
* this modelclass $summary_fields
|
2011-12-06 01:56:24 +01:00
|
|
|
*
|
2012-01-07 05:18:00 +01:00
|
|
|
* @param string $modelClassName
|
2012-04-19 01:25:37 +02:00
|
|
|
* @see GridFieldDataColumns::getDisplayFields()
|
2011-12-06 01:56:24 +01:00
|
|
|
*/
|
|
|
|
public function setModelClass($modelClassName) {
|
|
|
|
$this->modelClassName = $modelClassName;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-01-07 05:18:00 +01:00
|
|
|
* Returns a dataclass that is a DataObject type that this GridField should look like.
|
2011-12-06 01:56:24 +01:00
|
|
|
*
|
|
|
|
* @throws Exception
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getModelClass() {
|
|
|
|
if ($this->modelClassName) return $this->modelClassName;
|
2012-01-10 04:59:28 +01:00
|
|
|
if ($this->list && method_exists($this->list, 'dataClass')) {
|
|
|
|
$class = $this->list->dataClass();
|
|
|
|
if($class) return $class;
|
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
throw new LogicException('GridField doesn\'t have a modelClassName,'
|
|
|
|
. ' so it doesn\'t know the columns of this grid.');
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-01-07 05:18:00 +01:00
|
|
|
* Get the GridFieldConfig
|
2011-12-06 01:56:24 +01:00
|
|
|
*
|
|
|
|
* @return GridFieldConfig
|
|
|
|
*/
|
|
|
|
public function getConfig() {
|
|
|
|
return $this->config;
|
|
|
|
}
|
2012-07-04 05:03:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param GridFieldConfig $config
|
|
|
|
* @return GridField
|
|
|
|
*/
|
|
|
|
public function setConfig(GridFieldConfig $config) {
|
|
|
|
$this->config = $config;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-05-18 03:35:53 +02:00
|
|
|
public function getComponents() {
|
|
|
|
return $this->config->getComponents();
|
|
|
|
}
|
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
/**
|
2012-01-07 04:48:49 +01:00
|
|
|
* Cast a arbitrary value with the help of a castingDefintion
|
2011-12-06 01:56:24 +01:00
|
|
|
*
|
2012-01-07 04:48:49 +01:00
|
|
|
* @param $value
|
|
|
|
* @param $castingDefinition
|
|
|
|
* @todo refactor this into GridFieldComponent
|
2011-12-06 01:56:24 +01:00
|
|
|
*/
|
|
|
|
public function getCastedValue($value, $castingDefinition) {
|
|
|
|
if(is_array($castingDefinition)) {
|
|
|
|
$castingParams = $castingDefinition;
|
|
|
|
array_shift($castingParams);
|
|
|
|
$castingDefinition = array_shift($castingDefinition);
|
|
|
|
} else {
|
|
|
|
$castingParams = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(strpos($castingDefinition,'->') === false) {
|
|
|
|
$castingFieldType = $castingDefinition;
|
2012-03-27 06:57:42 +02:00
|
|
|
$castingField = DBField::create_field($castingFieldType, $value);
|
2011-12-06 01:56:24 +01:00
|
|
|
$value = call_user_func_array(array($castingField,'XML'),$castingParams);
|
|
|
|
} else {
|
|
|
|
$fieldTypeParts = explode('->', $castingDefinition);
|
|
|
|
$castingFieldType = $fieldTypeParts[0];
|
|
|
|
$castingMethod = $fieldTypeParts[1];
|
2012-03-27 06:57:42 +02:00
|
|
|
$castingField = DBField::create_field($castingFieldType, $value);
|
2011-12-06 01:56:24 +01:00
|
|
|
$value = call_user_func_array(array($castingField,$castingMethod),$castingParams);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the datasource
|
|
|
|
*
|
|
|
|
* @param SS_List $list
|
|
|
|
*/
|
|
|
|
public function setList(SS_List $list) {
|
|
|
|
$this->list = $list;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the datasource
|
|
|
|
*
|
|
|
|
* @return SS_List
|
|
|
|
*/
|
|
|
|
public function getList() {
|
|
|
|
return $this->list;
|
|
|
|
}
|
2012-09-06 13:42:48 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the datasource after applying the {@link GridField_DataManipulator}s to it.
|
|
|
|
*
|
|
|
|
* @return SS_List
|
|
|
|
*/
|
|
|
|
public function getManipulatedList() {
|
|
|
|
$list = $this->getList();
|
|
|
|
foreach($this->getComponents() as $item) {
|
|
|
|
if($item instanceof GridField_DataManipulator) {
|
|
|
|
$list = $item->getManipulatedData($this, $list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $list;
|
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
|
|
|
|
/**
|
2012-01-07 05:18:00 +01:00
|
|
|
* Get the current GridState_Data or the GridState
|
2011-12-06 01:56:24 +01:00
|
|
|
*
|
2012-01-07 04:48:49 +01:00
|
|
|
* @param bool $getData - flag for returning the GridState_Data or the GridState
|
|
|
|
* @return GridState_data|GridState
|
2011-12-06 01:56:24 +01:00
|
|
|
*/
|
|
|
|
public function getState($getData=true) {
|
|
|
|
if($getData) {
|
|
|
|
return $this->state->getData();
|
|
|
|
}
|
|
|
|
return $this->state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-01-07 05:18:00 +01:00
|
|
|
* Returns the whole gridfield rendered with all the attached components
|
2011-12-06 01:56:24 +01:00
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2012-04-11 08:07:55 +02:00
|
|
|
public function FieldHolder($properties = array()) {
|
2012-03-06 16:58:13 +01:00
|
|
|
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
|
2012-03-24 04:38:57 +01:00
|
|
|
Requirements::css(FRAMEWORK_DIR . '/css/GridField.css');
|
2012-03-06 16:58:13 +01:00
|
|
|
|
|
|
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
|
2012-03-24 04:38:57 +01:00
|
|
|
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery-ui/jquery-ui.js');
|
2012-03-06 16:58:13 +01:00
|
|
|
Requirements::javascript(THIRDPARTY_DIR . '/json-js/json2.js');
|
2012-03-24 04:38:57 +01:00
|
|
|
Requirements::javascript(FRAMEWORK_DIR . '/javascript/i18n.js');
|
|
|
|
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
|
2012-03-06 16:58:13 +01:00
|
|
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
|
2012-03-24 04:38:57 +01:00
|
|
|
Requirements::javascript(FRAMEWORK_DIR . '/javascript/GridField.js');
|
2012-03-06 16:58:13 +01:00
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
// Get columns
|
|
|
|
$columns = $this->getColumns();
|
|
|
|
|
|
|
|
// Get data
|
2012-09-06 13:42:48 +02:00
|
|
|
$list = $this->getManipulatedList();
|
2011-12-06 01:56:24 +01:00
|
|
|
|
|
|
|
// Render headers, footers, etc
|
|
|
|
$content = array(
|
2012-03-08 07:04:07 +01:00
|
|
|
"before" => "",
|
|
|
|
"after" => "",
|
|
|
|
"header" => "",
|
|
|
|
"footer" => "",
|
2011-12-06 01:56:24 +01:00
|
|
|
);
|
|
|
|
|
2012-05-18 03:35:53 +02:00
|
|
|
foreach($this->getComponents() as $item) {
|
2011-12-06 01:56:24 +01:00
|
|
|
if($item instanceof GridField_HTMLProvider) {
|
|
|
|
$fragments = $item->getHTMLFragments($this);
|
2012-03-09 02:32:16 +01:00
|
|
|
if($fragments) foreach($fragments as $k => $v) {
|
2012-03-08 07:04:07 +01:00
|
|
|
$k = strtolower($k);
|
|
|
|
if(!isset($content[$k])) $content[$k] = "";
|
|
|
|
$content[$k] .= $v . "\n";
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-08 07:04:07 +01:00
|
|
|
foreach($content as $k => $v) {
|
|
|
|
$content[$k] = trim($v);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace custom fragments and check which fragments are defined
|
|
|
|
// Nested dependencies are handled by deferring the rendering of any content item that
|
|
|
|
// Circular dependencies are detected by disallowing any item to be deferred more than 5 times
|
|
|
|
// It's a fairly crude algorithm but it works
|
|
|
|
|
2012-05-21 04:41:46 +02:00
|
|
|
$fragmentDefined = array('header' => true, 'footer' => true, 'before' => true, 'after' => true);
|
2012-03-08 07:04:07 +01:00
|
|
|
reset($content);
|
|
|
|
while(list($k,$v) = each($content)) {
|
|
|
|
if(preg_match_all('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $v, $matches)) {
|
|
|
|
foreach($matches[1] as $match) {
|
|
|
|
$fragmentName = strtolower($match);
|
|
|
|
$fragmentDefined[$fragmentName] = true;
|
|
|
|
$fragment = isset($content[$fragmentName]) ? $content[$fragmentName] : "";
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
// If the fragment still has a fragment definition in it, when we should defer this item until
|
|
|
|
// later.
|
2012-03-08 07:04:07 +01:00
|
|
|
if(preg_match('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $fragment, $matches)) {
|
|
|
|
// If we've already deferred this fragment, then we have a circular dependency
|
2012-03-08 22:04:25 +01:00
|
|
|
if(isset($fragmentDeferred[$k]) && $fragmentDeferred[$k] > 5) {
|
|
|
|
throw new LogicException("GridField HTML fragment '$fragmentName' and '$matches[1]' " .
|
|
|
|
"appear to have a circular dependency.");
|
|
|
|
}
|
2012-03-08 07:04:07 +01:00
|
|
|
|
|
|
|
// Otherwise we can push to the end of the content array
|
|
|
|
unset($content[$k]);
|
|
|
|
$content[$k] = $v;
|
2012-03-08 22:04:25 +01:00
|
|
|
if(!isset($fragmentDeferred[$k])) {
|
|
|
|
$fragmentDeferred[$k] = 1;
|
|
|
|
} else {
|
|
|
|
$fragmentDeferred[$k]++;
|
|
|
|
}
|
2012-03-08 07:04:07 +01:00
|
|
|
break;
|
|
|
|
} else {
|
2012-09-26 23:34:00 +02:00
|
|
|
$content[$k] = preg_replace('/\$DefineFragment\(' . $fragmentName . '\)/i', $fragment,
|
|
|
|
$content[$k]);
|
2012-03-08 07:04:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for any undefined fragments, and if so throw an exception
|
|
|
|
// While we're at it, trim whitespace off the elements
|
|
|
|
foreach($content as $k => $v) {
|
2012-09-26 23:34:00 +02:00
|
|
|
if(empty($fragmentDefined[$k])) {
|
|
|
|
throw new LogicException("GridField HTML fragment '$k' was given content,"
|
|
|
|
. " but not defined. Perhaps there is a supporting GridField component you need to add?");
|
|
|
|
}
|
2012-03-08 07:04:07 +01:00
|
|
|
}
|
|
|
|
|
2012-03-08 20:25:21 +01:00
|
|
|
$total = $list->count();
|
|
|
|
if($total > 0) {
|
2012-03-08 22:20:53 +01:00
|
|
|
$rows = array();
|
2012-03-06 00:36:32 +01:00
|
|
|
foreach($list as $idx => $record) {
|
2012-03-08 01:58:53 +01:00
|
|
|
if(!$record->canView()) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-03-06 00:36:32 +01:00
|
|
|
$rowContent = '';
|
2012-03-08 01:58:53 +01:00
|
|
|
foreach($this->getColumns() as $column) {
|
2012-03-06 00:36:32 +01:00
|
|
|
$colContent = $this->getColumnContent($record, $column);
|
|
|
|
// A return value of null means this columns should be skipped altogether.
|
|
|
|
if($colContent === null) continue;
|
|
|
|
$colAttributes = $this->getColumnAttributes($record, $column);
|
|
|
|
$rowContent .= $this->createTag('td', $colAttributes, $colContent);
|
|
|
|
}
|
2012-03-08 20:25:21 +01:00
|
|
|
$classes = array('ss-gridfield-item');
|
|
|
|
if ($idx == 0) $classes[] = 'first';
|
|
|
|
if ($idx == $total-1) $classes[] = 'last';
|
|
|
|
$classes[] = ($idx % 2) ? 'even' : 'odd';
|
2012-03-06 00:36:32 +01:00
|
|
|
$row = $this->createTag(
|
|
|
|
'tr',
|
|
|
|
array(
|
2012-03-08 20:25:21 +01:00
|
|
|
"class" => implode(' ', $classes),
|
2012-03-06 00:36:32 +01:00
|
|
|
'data-id' => $record->ID,
|
2012-03-09 02:07:40 +01:00
|
|
|
// TODO Allow per-row customization similar to GridFieldDataColumns
|
2012-03-06 00:36:32 +01:00
|
|
|
'data-class' => $record->ClassName,
|
|
|
|
),
|
|
|
|
$rowContent
|
|
|
|
);
|
2012-03-08 22:20:53 +01:00
|
|
|
$rows[] = $row;
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
2012-03-08 22:20:53 +01:00
|
|
|
$content['body'] = implode("\n", $rows);
|
2012-03-08 01:58:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Display a message when the grid field is empty
|
|
|
|
if(!(isset($content['body']) && $content['body'])) {
|
2012-03-08 22:20:53 +01:00
|
|
|
$content['body'] = $this->createTag(
|
2012-03-06 00:36:32 +01:00
|
|
|
'tr',
|
|
|
|
array("class" => 'ss-gridfield-item ss-gridfield-no-items'),
|
2012-09-26 23:34:00 +02:00
|
|
|
$this->createTag(
|
|
|
|
'td',
|
|
|
|
array('colspan' => count($columns)),
|
|
|
|
_t('GridField.NoItemsFound', 'No items found'))
|
2012-02-07 20:55:54 +01:00
|
|
|
);
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Turn into the relevant parts of a table
|
2012-09-26 23:34:00 +02:00
|
|
|
$head = $content['header']
|
|
|
|
? $this->createTag('thead', array(), $content['header'])
|
|
|
|
: '';
|
|
|
|
$body = $content['body']
|
|
|
|
? $this->createTag('tbody', array('class' => 'ss-gridfield-items'), $content['body'])
|
|
|
|
: '';
|
|
|
|
$foot = $content['footer']
|
|
|
|
? $this->createTag('tfoot', array(), $content['footer'])
|
|
|
|
: '';
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-02-23 15:17:39 +01:00
|
|
|
$this->addExtraClass('ss-gridfield field');
|
2012-02-07 20:55:54 +01:00
|
|
|
$attrs = array_diff_key(
|
|
|
|
$this->getAttributes(),
|
|
|
|
array('value' => false, 'type' => false, 'name' => false)
|
|
|
|
);
|
2012-02-09 17:17:39 +01:00
|
|
|
$attrs['data-name'] = $this->getName();
|
2012-02-07 20:55:54 +01:00
|
|
|
$tableAttrs = array(
|
2011-12-06 01:56:24 +01:00
|
|
|
'id' => isset($this->id) ? $this->id : null,
|
2012-02-23 15:17:39 +01:00
|
|
|
'class' => 'ss-gridfield-table',
|
2012-01-10 01:05:20 +01:00
|
|
|
'cellpadding' => '0',
|
|
|
|
'cellspacing' => '0'
|
2011-12-06 01:56:24 +01:00
|
|
|
);
|
2012-05-17 03:22:24 +02:00
|
|
|
|
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
return
|
2012-02-07 20:55:54 +01:00
|
|
|
$this->createTag('fieldset', $attrs,
|
2012-03-08 07:04:07 +01:00
|
|
|
$content['before'] .
|
2012-02-07 20:55:54 +01:00
|
|
|
$this->createTag('table', $tableAttrs, $head."\n".$foot."\n".$body) .
|
2012-03-08 07:04:07 +01:00
|
|
|
$content['after']
|
2012-01-09 06:21:14 +01:00
|
|
|
);
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
2012-03-08 07:11:51 +01:00
|
|
|
|
2012-04-11 07:33:36 +02:00
|
|
|
public function Field($properties = array()) {
|
2012-04-11 08:07:55 +02:00
|
|
|
return $this->FieldHolder($properties);
|
2012-03-08 07:11:51 +01:00
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-02-09 11:16:29 +01:00
|
|
|
public function getAttributes() {
|
|
|
|
return array_merge(parent::getAttributes(), array('data-url' => $this->Link()));
|
|
|
|
}
|
|
|
|
|
2012-01-07 05:18:00 +01:00
|
|
|
/**
|
|
|
|
* Get the columns of this GridField, they are provided by attached GridField_ColumnProvider
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getColumns() {
|
2011-12-06 01:56:24 +01:00
|
|
|
// Get column list
|
|
|
|
$columns = array();
|
2012-05-18 03:35:53 +02:00
|
|
|
foreach($this->getComponents() as $item) {
|
2011-12-06 01:56:24 +01:00
|
|
|
if($item instanceof GridField_ColumnProvider) {
|
|
|
|
$item->augmentColumns($this, $columns);
|
|
|
|
}
|
|
|
|
}
|
2012-02-07 23:21:34 +01:00
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
return $columns;
|
|
|
|
}
|
2012-01-07 05:18:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the value from a column
|
|
|
|
*
|
|
|
|
* @param DataObject $record
|
|
|
|
* @param string $column
|
|
|
|
* @return string
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
public function getColumnContent($record, $column) {
|
|
|
|
// Build the column dispatch
|
2012-01-07 04:48:49 +01:00
|
|
|
if(!$this->columnDispatch) {
|
|
|
|
$this->buildColumnDispatch();
|
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-01-10 04:24:00 +01:00
|
|
|
if(!empty($this->columnDispatch[$column])) {
|
2012-02-07 23:21:34 +01:00
|
|
|
$content = "";
|
|
|
|
foreach($this->columnDispatch[$column] as $handler) {
|
|
|
|
$content .= $handler->getColumnContent($this, $record, $column);
|
|
|
|
}
|
|
|
|
return $content;
|
2011-12-06 01:56:24 +01:00
|
|
|
} else {
|
|
|
|
throw new InvalidArgumentException("Bad column '$column'");
|
|
|
|
}
|
|
|
|
}
|
2012-05-29 00:06:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add additional calculated data fields to be used on this GridField
|
|
|
|
* @param array $fields a map of fieldname to callback. The callback will bed passed the record as an argument.
|
|
|
|
*/
|
|
|
|
public function addDataFields($fields) {
|
|
|
|
if($this->customDataFields) $this->customDataFields = array_merge($this->customDataFields, $fields);
|
|
|
|
else $this->customDataFields = $fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the value of a named field on the given record.
|
|
|
|
* Use of this method ensures that any special rules around the data for this gridfield are followed.
|
|
|
|
*/
|
|
|
|
public function getDataFieldValue($record, $fieldName) {
|
|
|
|
// Custom callbacks
|
|
|
|
if(isset($this->customDataFields[$fieldName])) {
|
|
|
|
$callback = $this->customDataFields[$fieldName];
|
|
|
|
return $callback($record);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default implementation
|
2012-06-15 13:33:34 +02:00
|
|
|
if($record->hasMethod('relField')) {
|
|
|
|
return $record->relField($fieldName);
|
|
|
|
} elseif($record->hasMethod($fieldName)) {
|
|
|
|
return $record->$fieldName();
|
|
|
|
} else {
|
|
|
|
return $record->$fieldName;
|
|
|
|
}
|
2012-05-29 00:06:30 +02:00
|
|
|
}
|
2012-01-07 05:18:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get extra columns attributes used as HTML attributes
|
|
|
|
*
|
|
|
|
* @param DataObject $record
|
|
|
|
* @param string $column
|
|
|
|
* @return array
|
|
|
|
* @throws LogicException
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
public function getColumnAttributes($record, $column) {
|
|
|
|
// Build the column dispatch
|
2012-01-07 04:48:49 +01:00
|
|
|
if(!$this->columnDispatch) {
|
|
|
|
$this->buildColumnDispatch();
|
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-01-10 04:24:00 +01:00
|
|
|
if(!empty($this->columnDispatch[$column])) {
|
2012-02-07 23:21:34 +01:00
|
|
|
$attrs = array();
|
|
|
|
|
|
|
|
foreach($this->columnDispatch[$column] as $handler) {
|
|
|
|
$column_attrs = $handler->getColumnAttributes($this, $record, $column);
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
if(is_array($column_attrs)) {
|
2012-02-07 23:21:34 +01:00
|
|
|
$attrs = array_merge($attrs, $column_attrs);
|
2012-09-26 23:34:00 +02:00
|
|
|
} elseif($column_attrs) {
|
|
|
|
$methodSignature = get_class($handler) . "::getColumnAttributes()";
|
|
|
|
throw new LogicException("Non-array response from $methodSignature.");
|
|
|
|
}
|
2012-01-07 04:48:49 +01:00
|
|
|
}
|
2012-02-07 23:21:34 +01:00
|
|
|
|
|
|
|
return $attrs;
|
2011-12-06 01:56:24 +01:00
|
|
|
} else {
|
|
|
|
throw new InvalidArgumentException("Bad column '$column'");
|
|
|
|
}
|
|
|
|
}
|
2012-01-07 05:18:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get metadata for a column, example array('Title'=>'Email address')
|
|
|
|
*
|
|
|
|
* @param string $column
|
|
|
|
* @return array
|
|
|
|
* @throws LogicException
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
public function getColumnMetadata($column) {
|
|
|
|
// Build the column dispatch
|
2012-01-07 04:48:49 +01:00
|
|
|
if(!$this->columnDispatch) {
|
|
|
|
$this->buildColumnDispatch();
|
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-01-10 04:24:00 +01:00
|
|
|
if(!empty($this->columnDispatch[$column])) {
|
2012-02-07 23:21:34 +01:00
|
|
|
$metadata = array();
|
|
|
|
|
|
|
|
foreach($this->columnDispatch[$column] as $handler) {
|
|
|
|
$column_metadata = $handler->getColumnMetadata($this, $column);
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
if(is_array($column_metadata)) {
|
2012-02-07 23:21:34 +01:00
|
|
|
$metadata = array_merge($metadata, $column_metadata);
|
2012-09-26 23:34:00 +02:00
|
|
|
} else {
|
|
|
|
$methodSignature = get_class($handler) . "::getColumnMetadata()";
|
|
|
|
throw new LogicException("Non-array response from $methodSignature.");
|
|
|
|
}
|
2012-02-07 23:21:34 +01:00
|
|
|
|
2012-01-07 04:48:49 +01:00
|
|
|
}
|
2012-02-07 23:21:34 +01:00
|
|
|
|
|
|
|
return $metadata;
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
2012-01-07 04:48:49 +01:00
|
|
|
throw new InvalidArgumentException("Bad column '$column'");
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
2012-01-07 05:18:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return how many columns the grid will have
|
|
|
|
*
|
|
|
|
* @return int
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
public function getColumnCount() {
|
|
|
|
// Build the column dispatch
|
|
|
|
if(!$this->columnDispatch) $this->buildColumnDispatch();
|
|
|
|
return count($this->columnDispatch);
|
|
|
|
}
|
2012-01-07 04:48:49 +01:00
|
|
|
|
2012-01-07 05:18:00 +01:00
|
|
|
/**
|
|
|
|
* Build an columnDispatch that maps a GridField_ColumnProvider to a column
|
|
|
|
* for reference later
|
|
|
|
*
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
protected function buildColumnDispatch() {
|
|
|
|
$this->columnDispatch = array();
|
2012-05-18 03:35:53 +02:00
|
|
|
foreach($this->getComponents() as $item) {
|
2011-12-06 01:56:24 +01:00
|
|
|
if($item instanceof GridField_ColumnProvider) {
|
|
|
|
$columns = $item->getColumnsHandled($this);
|
|
|
|
foreach($columns as $column) {
|
2012-02-07 23:21:34 +01:00
|
|
|
$this->columnDispatch[$column][] = $item;
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
}
|
2012-02-07 23:21:34 +01:00
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
2012-02-09 11:16:29 +01:00
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
/**
|
|
|
|
* This is the action that gets executed when a GridField_AlterAction gets clicked.
|
|
|
|
*
|
|
|
|
* @param array $data
|
|
|
|
* @return string
|
|
|
|
*/
|
2012-01-09 06:21:14 +01:00
|
|
|
public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) {
|
2012-02-09 11:16:29 +01:00
|
|
|
$html = '';
|
|
|
|
$data = $request->requestVars();
|
2012-02-09 17:17:39 +01:00
|
|
|
$fieldData = @$data[$this->getName()];
|
2012-02-09 11:16:29 +01:00
|
|
|
|
|
|
|
// Update state from client
|
|
|
|
$state = $this->getState(false);
|
|
|
|
if(isset($fieldData['GridState'])) $state->setValue($fieldData['GridState']);
|
|
|
|
|
|
|
|
// Try to execute alter action
|
|
|
|
foreach($data as $k => $v) {
|
|
|
|
if(preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $k, $matches)) {
|
|
|
|
$id = $matches[1];
|
|
|
|
$stateChange = Session::get($id);
|
|
|
|
$actionName = $stateChange['actionName'];
|
|
|
|
$args = isset($stateChange['args']) ? $stateChange['args'] : array();
|
|
|
|
$html = $this->handleAction($actionName, $args, $data);
|
|
|
|
// A field can optionally return its own HTML
|
|
|
|
if($html) return $html;
|
|
|
|
}
|
2012-01-25 05:31:27 +01:00
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
|
2012-04-10 00:34:04 +02:00
|
|
|
switch($request->getHeader('X-Pjax')) {
|
2012-01-09 06:21:14 +01:00
|
|
|
case 'CurrentField':
|
2012-02-09 11:16:29 +01:00
|
|
|
return $this->FieldHolder();
|
2012-01-09 06:21:14 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'CurrentForm':
|
|
|
|
return $form->forTemplate();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return $form->forTemplate();
|
|
|
|
break;
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
}
|
2012-01-07 05:18:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Pass an action on the first GridField_ActionProvider that matches the $actionName
|
|
|
|
*
|
|
|
|
* @param string $actionName
|
|
|
|
* @param mixed $args
|
|
|
|
* @param arrray $data - send data from a form
|
|
|
|
* @return type
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*/
|
2011-12-06 01:56:24 +01:00
|
|
|
public function handleAction($actionName, $args, $data) {
|
|
|
|
$actionName = strtolower($actionName);
|
2012-05-18 03:35:53 +02:00
|
|
|
foreach($this->getComponents() as $component) {
|
2012-01-25 05:31:27 +01:00
|
|
|
if(!($component instanceof GridField_ActionProvider)) {
|
2011-12-06 01:56:24 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-03-09 05:50:32 +01:00
|
|
|
if(in_array($actionName, array_map('strtolower', (array)$component->getActions($this)))) {
|
2012-01-25 05:31:27 +01:00
|
|
|
return $component->handleAction($this, $actionName, $args, $data);
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new InvalidArgumentException("Can't handle action '$actionName'");
|
|
|
|
}
|
2012-01-09 06:47:31 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Custom request handler that will check component handlers before proceeding to the default implementation.
|
|
|
|
*
|
|
|
|
* @todo There is too much code copied from RequestHandler here.
|
|
|
|
*/
|
2012-09-19 12:07:39 +02:00
|
|
|
public function handleRequest(SS_HTTPRequest $request, DataModel $model) {
|
2012-01-09 06:47:31 +01:00
|
|
|
if($this->brokenOnConstruct) {
|
|
|
|
user_error("parent::__construct() needs to be called on {$handlerClass}::__construct()", E_USER_WARNING);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->request = $request;
|
2012-05-01 04:43:52 +02:00
|
|
|
$this->setDataModel($model);
|
2012-02-09 11:16:29 +01:00
|
|
|
|
2012-02-09 17:17:39 +01:00
|
|
|
$fieldData = $this->request->requestVar($this->getName());
|
2012-02-09 11:16:29 +01:00
|
|
|
if($fieldData && $fieldData['GridState']) $this->getState(false)->setValue($fieldData['GridState']);
|
2012-01-09 06:47:31 +01:00
|
|
|
|
2012-05-18 03:35:53 +02:00
|
|
|
foreach($this->getComponents() as $component) {
|
2012-01-09 06:47:31 +01:00
|
|
|
if(!($component instanceof GridField_URLHandler)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$urlHandlers = $component->getURLHandlers($this);
|
|
|
|
|
|
|
|
if($urlHandlers) foreach($urlHandlers as $rule => $action) {
|
|
|
|
if($params = $request->match($rule, true)) {
|
|
|
|
// Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action',
|
|
|
|
if($action[0] == '$') $action = $params[substr($action,1)];
|
|
|
|
if(!method_exists($component, 'checkAccessAction') || $component->checkAccessAction($action)) {
|
|
|
|
if(!$action) {
|
|
|
|
$action = "index";
|
|
|
|
} else if(!is_string($action)) {
|
|
|
|
throw new LogicException("Non-string method name: " . var_export($action, true));
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$result = $component->$action($this, $request);
|
|
|
|
} catch(SS_HTTPResponse_Exception $responseException) {
|
|
|
|
$result = $responseException->getResponse();
|
|
|
|
}
|
|
|
|
|
|
|
|
if($result instanceof SS_HTTPResponse && $result->isError()) {
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result)
|
|
|
|
&& $result instanceof RequestHandler) {
|
|
|
|
|
2012-01-09 06:47:31 +01:00
|
|
|
$returnValue = $result->handleRequest($request, $model);
|
|
|
|
|
|
|
|
if(is_array($returnValue)) {
|
|
|
|
throw new LogicException("GridField_URLHandler handlers can't return arrays");
|
|
|
|
}
|
|
|
|
|
|
|
|
return $returnValue;
|
|
|
|
|
|
|
|
// If we return some other data, and all the URL is parsed, then return that
|
|
|
|
} else if($request->allParsed()) {
|
|
|
|
return $result;
|
|
|
|
|
2012-09-26 23:34:00 +02:00
|
|
|
// But if we have more content on the URL and we don't know what to do with it, return an error
|
2012-01-09 06:47:31 +01:00
|
|
|
} else {
|
2012-09-26 23:34:00 +02:00
|
|
|
return $this->httpError(404,
|
|
|
|
"I can't handle sub-URLs of a " . get_class($result) . " object.");
|
2012-01-09 06:47:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return parent::handleRequest($request, $model);
|
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-03-06 12:51:57 +01:00
|
|
|
* This class is the base class when you want to have an action that alters the state of the gridfield,
|
|
|
|
* rendered as a button element.
|
2011-12-06 01:56:24 +01:00
|
|
|
*
|
2012-04-12 08:02:46 +02:00
|
|
|
* @package framework
|
2011-12-06 01:56:24 +01:00
|
|
|
* @subpackage forms
|
|
|
|
*
|
|
|
|
*/
|
2012-03-06 12:51:57 +01:00
|
|
|
class GridField_FormAction extends FormAction {
|
2011-12-06 01:56:24 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @var GridField
|
|
|
|
*/
|
|
|
|
protected $gridField;
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $stateValues;
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
//protected $stateFields = array();
|
|
|
|
|
|
|
|
protected $actionName;
|
2012-03-06 16:58:13 +01:00
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
protected $args = array();
|
|
|
|
|
2012-03-06 16:58:13 +01:00
|
|
|
public $useButtonTag = true;
|
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param GridField $gridField
|
|
|
|
* @param type $name
|
|
|
|
* @param type $label
|
|
|
|
* @param type $actionName
|
|
|
|
* @param type $args
|
|
|
|
*/
|
2012-03-06 16:58:13 +01:00
|
|
|
public function __construct(GridField $gridField, $name, $title, $actionName, $args) {
|
2011-12-06 01:56:24 +01:00
|
|
|
$this->gridField = $gridField;
|
|
|
|
$this->actionName = $actionName;
|
|
|
|
$this->args = $args;
|
2012-03-06 16:58:13 +01:00
|
|
|
parent::__construct($name, $title);
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* urlencode encodes less characters in percent form than we need - we need everything that isn't a \w
|
|
|
|
*
|
|
|
|
* @param string $val
|
|
|
|
*/
|
|
|
|
public function nameEncode($val) {
|
|
|
|
return preg_replace_callback('/[^\w]/', array($this, '_nameEncode'), $val);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The callback for nameEncode
|
|
|
|
*
|
|
|
|
* @param string $val
|
|
|
|
*/
|
|
|
|
public function _nameEncode($match) {
|
|
|
|
return '%'.dechex(ord($match[0]));
|
|
|
|
}
|
|
|
|
|
2012-03-06 16:58:13 +01:00
|
|
|
public function getAttributes() {
|
2011-12-06 01:56:24 +01:00
|
|
|
// Store state in session, and pass ID to client side
|
|
|
|
$state = array(
|
|
|
|
'grid' => $this->getNameFromParent(),
|
|
|
|
'actionName' => $this->actionName,
|
|
|
|
'args' => $this->args,
|
|
|
|
);
|
|
|
|
$id = preg_replace('/[^\w]+/', '_', uniqid('', true));
|
|
|
|
Session::set($id, $state);
|
|
|
|
$actionData['StateID'] = $id;
|
|
|
|
|
2012-03-06 16:58:13 +01:00
|
|
|
return array_merge(
|
|
|
|
parent::getAttributes(),
|
|
|
|
array(
|
|
|
|
// Note: This field needs to be less than 65 chars, otherwise Suhosin security patch
|
|
|
|
// will strip it from the requests
|
|
|
|
'name' => 'action_gridFieldAlterAction'. '?' . http_build_query($actionData),
|
|
|
|
'data-url' => $this->gridField->Link(),
|
|
|
|
)
|
|
|
|
);
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the name of the gridfield relative to the Form
|
|
|
|
*
|
|
|
|
* @param GridField $base
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getNameFromParent() {
|
|
|
|
$base = $this->gridField;
|
|
|
|
$name = array();
|
|
|
|
do {
|
|
|
|
array_unshift($name, $base->getName());
|
|
|
|
$base = $base->getForm();
|
|
|
|
} while ($base && !($base instanceof Form));
|
|
|
|
return implode('.', $name);
|
|
|
|
}
|
|
|
|
}
|