Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1645 lines (1395 sloc) 46.43 kb
<?php
/**
* @package forms
* @subpackage fields-relational
*/
/**
* Form field that embeds a list into a form, such as a member list or a file list.
*
* All get variables are namespaced in the format ctf[MyFieldName][MyParameter] to avoid collisions
* when multiple TableListFields are present in a form.
*
* @deprecated 3.0 Use GridField with GridFieldConfig_RecordEditor
*
* @package forms
* @subpackage fields-relational
*/
class TableListField extends FormField {
/**
* The {@link DataList} object defining the source data for this view/
*/
protected $dataList;
protected $fieldList;
protected $disableSorting = false;
/**
* @var $fieldListCsv array
*/
protected $fieldListCsv;
/**
* @var $clickAction
*/
protected $clickAction;
/**
* @var bool
*/
public $IsReadOnly;
/**
* Called method (needs to be retained for AddMode())
*/
protected $methodName;
/**
* @var $summaryFieldList array Shows a row which summarizes the contents of a column by a predefined
* Javascript-function
*/
protected $summaryFieldList;
/**
* @var $summaryTitle string The title which will be shown in the first column of the summary-row.
* Accordingly, the first column can't be used for summarizing.
*/
protected $summaryTitle;
/**
* @var $template string Template-Overrides
*/
protected $template = "TableListField";
/**
* @var $itemClass string Class name for each item/row
*/
public $itemClass = 'TableListField_Item';
/**
* @var bool Do we use checkboxes to mark records, or delete them one by one?
*/
public $Markable;
public $MarkableTitle = null;
/**
* @var array See {@link SelectOptions()}
*/
protected $selectOptions = array();
/**
* @var $readOnly boolean Deprecated, please use $permssions instead
*/
protected $readOnly;
/**
* @var $permissions array Influence output without having to subclass the template.
* See $actions for adding your custom actions/permissions.
*/
protected $permissions = array(
//"print",
//"export",
"delete"
);
/**
* @var $actions array Action that can be performed on a single row-entry.
* Has to correspond to a method in a TableListField-class (or subclass).
* Actions can be disabled through $permissions.
* Format (key is used for the methodname and CSS-class):
* array(
* 'delete' => array(
* 'label' => 'Delete',
* 'icon' => 'framework/images/delete.gif',
* 'icon_disabled' => 'framework/images/delete_disabled.gif',
* 'class' => 'deletelink',
* )
* )
*/
public $actions = array(
'delete' => array(
'label' => 'Delete',
'icon' => 'framework/images/delete.gif',
'icon_disabled' => 'framework/images/delete_disabled.gif',
'class' => 'deletelink'
)
);
/**
* @var $defaultAction String Action being executed when clicking on table-row (defaults to "show").
* Mostly needed in ComplexTableField-subclass.
*/
public $defaultAction = '';
/**
* @var $customCsvQuery Query for CSV-export (might need different fields or further filtering)
*/
protected $customCsvQuery;
/**
* Character to seperate exported columns in the CSV file
*/
protected $csvSeparator = ",";
/*
* Boolean deciding whether to include a header row in the CSV file
*/
protected $csvHasHeader = true;
/**
* @var array Specify custom escape for the fields.
*
* <code>
* array("\""=>"\"\"","\r"=>"", "\r\n"=>"", "\n"=>"")
* </code>
*/
public $csvFieldEscape = array(
"\""=>"\"\"",
"\r\n"=>"",
"\r"=>"",
"\n"=>"",
);
/**
* @var boolean Trigger pagination
*/
protected $showPagination = false;
/**
* @var string Override the {@link Link()} method
* for all pagination. Useful to force rendering of the field
* in a different context.
*/
public $paginationBaseLink = null;
/**
* @var int Number of items to show on a single page (needed for pagination)
*/
protected $pageSize = 10;
/**
* @var array Definitions for highlighting table-rows with a specific class. You can use all column-names
* in the result of a query. Use in combination with {@setCustomQuery} to select custom properties and joined
* objects.
*
* Example:
* array(
* array(
* "rule" => '$Flag == "red"',
* "class" => "red"
* ),
* array(
* "rule" => '$Flag == "orange"',
* "class" => "orange"
* )
* )
*/
public $highlightConditions = array();
/**
* @var array Specify castings with fieldname as the key, and the desired casting as value.
* Example: array("MyCustomDate"=>"Date","MyShortText"=>"Text->FirstSentence")
*/
public $fieldCasting = array();
/**
* @var array Specify custom formatting for fields, e.g. to render a link instead of pure text.
* Caution: Make sure to escape special php-characters like in a normal php-statement.
* Example: "myFieldName" => '<a href=\"custom-admin/$ID\">$ID</a>'
*/
public $fieldFormatting = array();
public $csvFieldFormatting = array();
/**
* @var string
*/
public $exportButtonLabel = 'Export as CSV';
/**
* @var string $groupByField Used to group by a specific column in the DataObject
* and create partial summaries.
*/
public $groupByField = null;
/**
* @var array
*/
protected $extraLinkParams;
protected $__cachedQuery;
/**
* This is a flag that enables some backward-compatibility helpers.
*/
private $getDataListFromForm;
/**
* @param $name string The fieldname
* @param $sourceClass string The source class of this field
* @param $fieldList array An array of field headings of Fieldname => Heading Text (eg. heading1)
* @param $sourceFilter string The filter field you wish to limit the objects by (eg. parentID)
* @param $sourceSort string
* @param $sourceJoin string
*/
public function __construct($name, $sourceClass = null, $fieldList = null, $sourceFilter = null,
$sourceSort = null, $sourceJoin = null) {
if($sourceClass) {
// You can optionally pass a list
if($sourceClass instanceof SS_List) {
$this->dataList = $sourceClass;
} else {
$this->dataList = DataObject::get($sourceClass)->where($sourceFilter)->sort($sourceSort);
if($sourceJoin) $this->dataList = $this->dataList->join($sourceJoin);
// Grab it from the form relation, if available.
$this->getDataListFromForm = true;
}
}
$this->fieldList = ($fieldList) ? $fieldList : singleton($this->sourceClass())->summaryFields();
$this->readOnly = false;
parent::__construct($name);
}
public function index() {
return $this->FieldHolder();
}
static $url_handlers = array(
'item/$ID' => 'handleItem',
'$Action' => '$Action',
);
public function sourceClass() {
$list = $this->getDataList();
if(method_exists($list, 'dataClass')) return $list->dataClass();
// Failover for SS_List
else return get_class($list->First());
}
public function handleItem($request) {
return new TableListField_ItemRequest($this, $request->param('ID'));
}
public function FieldHolder($properties = array()) {
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/prototype/prototype.js');
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/behaviour/behaviour.js');
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
Requirements::javascript(FRAMEWORK_DIR . '/javascript/TableListField.js');
Requirements::css(FRAMEWORK_DIR . '/css/TableListField.css');
if($this->clickAction) {
$id = $this->id();
Requirements::customScript(<<<JS
Behaviour.register({
'#$id tr' : {
onclick : function() {
$this->clickAction
return false;
}
}
});
JS
);}
$obj = $properties ? $this->customise($properties) : $this;
return $obj->renderWith($this->template);
}
public function Headings() {
$headings = array();
foreach($this->fieldList as $fieldName => $fieldTitle) {
$isSorted = (isset($_REQUEST['ctf'][$this->getName()]['sort'])
&& $fieldName == $_REQUEST['ctf'][$this->getName()]['sort']);
// we can't allow sorting with partial summaries (groupByField)
$isSortable = ($this->form && $this->isFieldSortable($fieldName) && !$this->groupByField);
// sorting links (only if we have a form to refresh with)
if($this->form) {
$sortLink = $this->Link();
$sortLink = HTTP::setGetVar("ctf[{$this->getName()}][sort]", $fieldName, $sortLink,'&');
// Apply sort direction to the current sort field
if(!empty($_REQUEST['ctf'][$this->getName()]['sort'])
&& ($_REQUEST['ctf'][$this->getName()]['sort'] == $fieldName)) {
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
$dir = $_REQUEST['ctf'][$this->getName()]['dir'];
} else {
$dir = null;
}
$dir = trim(strtolower($dir));
$newDir = ($dir == 'desc') ? null : 'desc';
$sortLink = HTTP::setGetVar("ctf[{$this->getName()}][dir]", Convert::raw2xml($newDir),
$sortLink,'&');
}
if(isset($_REQUEST['ctf'][$this->getName()]['search'])
&& is_array($_REQUEST['ctf'][$this->getName()]['search'])) {
foreach($_REQUEST['ctf'][$this->getName()]['search'] as $parameter => $value) {
$XML_search = Convert::raw2xml($value);
$sortLink = HTTP::setGetVar("ctf[{$this->getName()}][search][$parameter]", $XML_search,
$sortLink,'&');
}
}
} else {
$sortLink = '#';
}
$headings[] = new ArrayData(array(
"Name" => $fieldName,
"Title" => ($this->sourceClass())
? singleton($this->sourceClass())->fieldLabel($fieldTitle)
: $fieldTitle,
"IsSortable" => $isSortable,
"SortLink" => $sortLink,
"SortBy" => $isSorted,
"SortDirection" => (isset($_REQUEST['ctf'][$this->getName()]['dir']))
? $_REQUEST['ctf'][$this->getName()]['dir']
: null
));
}
return new ArrayList($headings);
}
public function disableSorting($to = true) {
$this->disableSorting = $to;
}
/**
* Determines if a field is "sortable".
* If the field is generated by a custom getter, we can't sort on it
* without generating all objects first (which would be a huge performance impact).
*
* @param string $fieldName
* @return bool
*/
public function isFieldSortable($fieldName) {
if($this->disableSorting) return false;
$list = $this->getDataList();
if(method_exists($list,'canSortBy')) return $list->canSortBy($fieldName);
else return false;
}
/**
* Dummy function to get number of actions originally generated in
* TableListField_Item.
*
* @return SS_List
*/
public function Actions() {
$allowedActions = new ArrayList();
foreach($this->actions as $actionName => $actionSettings) {
if($this->Can($actionName)) {
$allowedActions->push(new ViewableData());
}
}
return $allowedActions;
}
/**
* Provide a custom query to compute sourceItems. This is the preferred way to using
* {@setSourceItems}, because we can still paginate.
* Please use this only as a fallback for really complex queries (e.g. involving HAVING and GROUPBY).
*
* @param $query DataList
*/
public function setCustomQuery(DataList $dataList) {
$this->dataList = $dataList;
return $this;
}
public function setCustomCsvQuery(DataList $dataList) {
$this->customCsvQuery = $query;
return $this;
}
public function setCustomSourceItems(SS_List $items) {
user_error('TableList::setCustomSourceItems() deprecated, just pass the items into the constructor',
E_USER_WARNING);
// The type-hinting above doesn't seem to work consistently
if($items instanceof SS_List) {
$this->dataList = $items;
} else {
user_error('TableList::setCustomSourceItems() should be passed a SS_List', E_USER_WARNING);
}
return $this;
}
/**
* Get items, with sort & limit applied
*/
public function sourceItems() {
// get items (this may actually be a SS_List)
$items = clone $this->getDataList();
// TODO: Sorting could be implemented on regular SS_Lists.
if(method_exists($items,'canSortBy') && isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
$sort = $_REQUEST['ctf'][$this->getName()]['sort'];
// TODO: sort direction
if($items->canSortBy($sort)) $items = $items->sort($sort);
}
// Determine pagination limit, offset
// To disable pagination, set $this->showPagination to false.
if($this->showPagination && $this->pageSize) {
$SQL_limit = (int)$this->pageSize;
if(isset($_REQUEST['ctf'][$this->getName()]['start'])
&& is_numeric($_REQUEST['ctf'][$this->getName()]['start'])) {
if(isset($_REQUEST['ctf'][$this->getName()]['start'])) {
$SQL_start = intval($_REQUEST['ctf'][$this->getName()]['start']);
} else {
$SQL_start = "0";
}
} else {
$SQL_start = 0;
}
$items = $items->limit($SQL_limit, $SQL_start);
}
return $items;
}
/**
* Return a SS_List of TableListField_Item objects, suitable for display in the template.
*/
public function Items() {
$fieldItems = new ArrayList();
if($items = $this->sourceItems()) foreach($items as $item) {
if($item) $fieldItems->push(new $this->itemClass($item, $this));
}
return $fieldItems;
}
/**
* Returns the DataList for this field.
*/
public function getDataList() {
// If we weren't passed in a DataList to begin with, try and get the datalist from the form
if($this->form && $this->getDataListFromForm) {
$this->getDataListFromForm = false;
$relation = $this->name;
if($record = $this->form->getRecord()) {
if($record->hasMethod($relation)) $this->dataList = $record->$relation();
}
}
if(!$this->dataList) {
user_error(get_class($this). ' is missing a DataList', E_USER_ERROR);
}
return $this->dataList;
}
public function getCsvDataList() {
if($this->customCsvQuery) return $this->customCsvQuery;
else return $this->getDataList();
}
/**
* @deprecated Use getDataList() instead.
*/
public function getQuery() {
Deprecation::notice('3.0', 'Use getDataList() instead.');
$list = $this->getDataList();
if(method_exists($list,'dataQuery')) {
return $this->getDataList()->dataQuery()->query();
}
}
/**
* @deprecated Use getCsvDataList() instead.
*/
public function getCsvQuery() {
Deprecation::notice('3.0', 'Use getCsvDataList() instead.');
$list = $this->getCsvDataList();
if(method_exists($list,'dataQuery')) {
return $list->dataQuery()->query();
}
}
public function FieldList() {
return $this->fieldList;
}
/**
* Configure this table to load content into a subform via ajax
*/
public function setClick_AjaxLoad($urlBase, $formID) {
$this->clickAction = "this.ajaxRequest('" . addslashes($urlBase) . "', '" . addslashes($formID) . "')";
return $this;
}
/**
* Configure this table to open a popup window
*/
public function setClick_PopupLoad($urlBase) {
$this->clickAction = "var w = window.open(baseHref()+'$urlBase'+this.id.replace(/.*-(\d*)$/,'$1'), 'popup');"
. " w.focus();";
return $this;
}
public function performReadonlyTransformation() {
$clone = clone $this;
$clone->setShowPagination(false);
// Only include the show action if it was in the original CTF.
$clone->setPermissions(in_array('show', $this->permissions) ? array('show') : array());
$clone->addExtraClass( 'readonly' );
$clone->setReadonly(true);
return $clone;
}
/**
* #################################
* CRUD
* #################################
*/
/**
* @return String
*/
public function delete($request) {
// Protect against CSRF on destructive action
$token = $this->getForm()->getSecurityToken();
if(!$token->checkRequest($request)) return $this->httpError('400');
if($this->Can('delete') !== true) {
return false;
}
$this->methodName = "delete";
$childId = Convert::raw2sql($_REQUEST['ctf']['childID']);
if (is_numeric($childId)) {
$this->getDataList()->removeById($childId);
}
// TODO return status in JSON etc.
//return $this->renderWith($this->template);
}
/**
* #################################
* Summary-Row
* #################################
*/
/**
* Can utilize some built-in summary-functions, with optional casting.
* Currently supported:
* - sum
* - avg
*
* @param $summaryTitle string
* @param $summaryFields array
* Simple Format: array("MyFieldName"=>"sum")
* With Casting: array("MyFieldname"=>array("sum","Currency->Nice"))
*/
public function addSummary($summaryTitle, $summaryFieldList) {
$this->summaryTitle = $summaryTitle;
$this->summaryFieldList = $summaryFieldList;
}
public function removeSummary() {
$this->summaryTitle = null;
$this->summaryFields = null;
}
public function HasSummary() {
return (isset($this->summaryFieldList));
}
public function SummaryTitle() {
return $this->summaryTitle;
}
/**
* @param SS_List $items Only used to pass grouped sourceItems for creating
* partial summaries.
*/
public function SummaryFields($items = null) {
if(!isset($this->summaryFieldList)) {
return false;
}
$summaryFields = array();
$fieldListWithoutFirst = $this->fieldList;
if(!empty($this->summaryTitle)) {
array_shift($fieldListWithoutFirst);
}
foreach($fieldListWithoutFirst as $fieldName => $fieldTitle) {
if(in_array($fieldName, array_keys($this->summaryFieldList))) {
if(is_array($this->summaryFieldList[$fieldName])) {
$summaryFunction = "colFunction_{$this->summaryFieldList[$fieldName][0]}";
$casting = $this->summaryFieldList[$fieldName][1];
} else {
$summaryFunction = "colFunction_{$this->summaryFieldList[$fieldName]}";
$casting = null;
}
// fall back to integrated sourceitems if not passed
if(!$items) $items = $this->sourceItems();
$summaryValue = ($items) ? $this->$summaryFunction($items->column($fieldName)) : null;
// Optional casting, Format: array('MyFieldName'=>array('sum','Currency->Nice'))
if(isset($casting)) {
$summaryValue = $this->getCastedValue($summaryValue, $casting);
}
} else {
$summaryValue = null;
$function = null;
}
$summaryFields[] = new ArrayData(array(
'Function' => $function,
'SummaryValue' => $summaryValue,
'Name' => DBField::create_field('Varchar', $fieldName),
'Title' => DBField::create_field('Varchar', $fieldTitle),
));
}
return new ArrayList($summaryFields);
}
public function HasGroupedItems() {
return ($this->groupByField);
}
public function GroupedItems() {
if(!$this->groupByField) {
return false;
}
$items = $this->sourceItems();
if(!$items || !$items->Count()) {
return false;
}
$groupedItems = $items->groupBy($this->groupByField);
$groupedArrItems = new ArrayList();
foreach($groupedItems as $key => $group) {
$fieldItems = new ArrayList();
foreach($group as $item) {
if($item) $fieldItems->push(new $this->itemClass($item, $this));
}
$groupedArrItems->push(new ArrayData(array(
'Items' => $fieldItems,
'SummaryFields' => $this->SummaryFields($group)
)));
}
return $groupedArrItems;
}
public function colFunction_sum($values) {
return array_sum($values);
}
public function colFunction_avg($values) {
return array_sum($values)/count($values);
}
/**
* #################################
* Permissions
* #################################
*/
/**
* Template accessor for Permissions.
* See {@link TableListField_Item->Can()} for object-specific
* permissions.
*
* @return boolean
*/
public function Can($mode) {
if($mode == 'add' && $this->isReadonly()) {
return false;
} else if($mode == 'delete' && $this->isReadonly()) {
return false;
} else if($mode == 'edit' && $this->isReadonly()) {
return false;
} else {
return (in_array($mode, $this->permissions));
}
}
public function setPermissions($arr) {
$this->permissions = $arr;
return $this;
}
/**
* @return array
*/
public function getPermissions() {
return $this->permissions;
}
/**
* #################################
* Pagination
* #################################
*/
public function setShowPagination($bool) {
$this->showPagination = (bool)$bool;
return $this;
}
/**
* @return boolean
*/
public function ShowPagination() {
if($this->showPagination && !empty($this->summaryFieldList)) {
user_error("You can't combine pagination and summaries - please disable one of them.", E_USER_ERROR);
}
return $this->showPagination;
}
public function setPageSize($pageSize) {
$this->pageSize = $pageSize;
return $this;
}
public function PageSize() {
return $this->pageSize;
}
public function ListStart() {
return $_REQUEST['ctf'][$this->getName()]['start'];
}
/**
* @param array
* @deprecated Put the query string onto your form's link instead :-)
*/
public function setExtraLinkParams($params){
Deprecation::notice('2.4', 'Put the query string onto your FormAction instead().');
$this->extraLinkParams = $params;
return $this;
}
/**
* @return array
*/
public function getExtraLinkParams(){
return $this->extraLinkParams;
}
public function FirstLink() {
$start = 0;
if(!isset($_REQUEST['ctf'][$this->getName()]['start'])
|| !is_numeric($_REQUEST['ctf'][$this->getName()]['start'])
|| $_REQUEST['ctf'][$this->getName()]['start'] == 0) {
return null;
}
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
// preserve sort options
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
// direction
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
}
}
return $link;
}
public function PrevLink() {
if(isset($_REQUEST['ctf'][$this->getName()]['start'])) {
$currentStart = $_REQUEST['ctf'][$this->getName()]['start'];
} else {
$currentStart = 0;
}
if($currentStart == 0) {
return null;
}
if($_REQUEST['ctf'][$this->getName()]['start'] - $this->pageSize < 0) {
$start = 0;
} else {
$start = $_REQUEST['ctf'][$this->getName()]['start'] - $this->pageSize;
}
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
// preserve sort options
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
// direction
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
}
}
return $link;
}
public function NextLink() {
$currentStart = isset($_REQUEST['ctf'][$this->getName()]['start'])
? $_REQUEST['ctf'][$this->getName()]['start']
: 0;
$start = ($currentStart + $this->pageSize < $this->TotalCount())
? $currentStart + $this->pageSize
: $this->TotalCount() % $this->pageSize > 0;
if($currentStart >= $start-1) {
return null;
}
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
// preserve sort options
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
// direction
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
}
}
return $link;
}
public function LastLink() {
$pageSize = ($this->TotalCount() % $this->pageSize > 0)
? $this->TotalCount() % $this->pageSize
: $this->pageSize;
$start = $this->TotalCount() - $pageSize;
// Check if there is only one page, or if we are on last page
if($this->TotalCount() <= $pageSize || (isset($_REQUEST['ctf'][$this->getName()]['start'])
&& $_REQUEST['ctf'][$this->getName()]['start'] >= $start)) {
return null;
}
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
// preserve sort options
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
// direction
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
}
}
return $link;
}
public function FirstItem() {
if ($this->TotalCount() < 1) return 0;
return isset($_REQUEST['ctf'][$this->getName()]['start'])
? $_REQUEST['ctf'][$this->getName()]['start'] + 1
: 1;
}
public function LastItem() {
if(isset($_REQUEST['ctf'][$this->getName()]['start'])) {
$pageStep = min($this->pageSize, $this->TotalCount() - $_REQUEST['ctf'][$this->getName()]['start']);
return $_REQUEST['ctf'][$this->getName()]['start'] + $pageStep;
} else {
return min($this->pageSize, $this->TotalCount());
}
}
/**
* @ignore
*/
private $_cache_TotalCount;
/**
* Return the total number of items in the source DataList
*/
public function TotalCount() {
if($this->_cache_TotalCount === null) {
$this->_cache_TotalCount = $this->getDataList()->Count();
}
return $this->_cache_TotalCount;
}
/**
* #################################
* Search
* #################################
*
* @todo Not fully implemented at the moment
*/
/**
* Compile all request-parameters for search and pagination
* (except the actual list-positions) as a query-string.
*
* @return String URL-parameters
*/
public function filterString() {
}
/**
* #################################
* CSV Export
* #################################
*/
public function setFieldListCsv($fields) {
$this->fieldListCsv = $fields;
return $this;
}
/**
* Set the CSV separator character. Defaults to ,
*/
public function setCsvSeparator($csvSeparator) {
$this->csvSeparator = $csvSeparator;
return $this;
}
/**
* Get the CSV separator character. Defaults to ,
*/
public function getCsvSeparator() {
return $this->csvSeparator;
}
/**
* Remove the header row from the CSV export
*/
public function removeCsvHeader() {
$this->csvHasHeader = false;
return $this;
}
/**
* Exports a given set of comma-separated IDs (from a previous search-query, stored in a HiddenField).
* Uses {$csv_columns} if present, and falls back to {$result_columns}.
* We move the most filedata generation code to the function {@link generateExportFileData()} so that a child class
* could reuse the filedata generation code while overwrite export function.
*
* @todo Make relation-syntax available (at the moment you'll have to use custom sql)
*/
public function export() {
$now = date("d-m-Y-H-i");
$fileName = "export-$now.csv";
// No pagination for export
$oldShowPagination = $this->showPagination;
$this->showPagination = false;
$result = $this->renderWith(array($this->template . '_printable', 'TableListField_printable'));
$this->showPagination = $oldShowPagination;
if($fileData = $this->generateExportFileData($numColumns, $numRows)){
return SS_HTTPRequest::send_file($fileData, $fileName, 'text/csv');
} else {
user_error("No records found", E_USER_ERROR);
}
}
public function generateExportFileData(&$numColumns, &$numRows) {
$separator = $this->csvSeparator;
$csvColumns = ($this->fieldListCsv) ? $this->fieldListCsv : $this->fieldList;
$fileData = '';
$columnData = array();
$fieldItems = new ArrayList();
if($this->csvHasHeader) {
$fileData .= "\"" . implode("\"{$separator}\"", array_values($csvColumns)) . "\"";
$fileData .= "\n";
}
if(isset($this->customSourceItems)) {
$items = $this->customSourceItems;
} else {
$items = $this->getCsvDataList();
}
// temporary override to adjust TableListField_Item behaviour
$this->setFieldFormatting(array());
$this->fieldList = $csvColumns;
if($items) {
foreach($items as $item) {
if(is_array($item)) {
$className = isset($item['RecordClassName']) ? $item['RecordClassName'] : $item['ClassName'];
$item = new $className($item);
}
$fieldItem = new $this->itemClass($item, $this);
$fields = $fieldItem->Fields(false);
$columnData = array();
if($fields) foreach($fields as $field) {
$value = $field->Value;
// TODO This should be replaced with casting
if(array_key_exists($field->Name, $this->csvFieldFormatting)) {
$format = str_replace('$value', "__VAL__", $this->csvFieldFormatting[$field->Name]);
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
$format = str_replace('__VAL__', '$value', $format);
eval('$value = "' . $format . '";');
}
$value = str_replace(array("\r", "\n"), "\n", $value);
$tmpColumnData = '"' . str_replace('"', '\"', $value) . '"';
$columnData[] = $tmpColumnData;
}
$fileData .= implode($separator, $columnData);
$fileData .= "\n";
$item->destroy();
unset($item);
unset($fieldItem);
}
$numColumns = count($columnData);
$numRows = $fieldItems->count();
return $fileData;
} else {
return null;
}
}
/**
* We need to instanciate this button manually as a normal button has no means of adding inline onclick-behaviour.
*/
public function ExportLink() {
$exportLink = Controller::join_links($this->Link(), 'export');
if($this->extraLinkParams) $exportLink .= "?" . http_build_query($this->extraLinkParams);
return $exportLink;
}
public function printall() {
Requirements::clear();
if(defined('CMS_DIR')) {
Requirements::css(CMS_DIR . '/css/typography.css');
Requirements::css(CMS_DIR . '/css/cms_right.css');
}
$this->cachedSourceItems = null;
$oldShowPagination = $this->showPagination;
$this->showPagination = false;
increase_time_limit_to();
$this->Print = true;
$result = $this->renderWith(array($this->template . '_printable', 'TableListField_printable'));
$this->showPagination = $oldShowPagination;
return $result;
}
public function PrintLink() {
$link = Controller::join_links($this->Link(), 'printall');
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
$link = HTTP::setGetVar("ctf[{$this->getName()}][sort]",
Convert::raw2xml($_REQUEST['ctf'][$this->getName()]['sort']), $link);
}
return $link;
}
/**
* #################################
* Utilty
* #################################
*/
public function Utility() {
$links = new ArrayList();
if($this->can('export')) {
$links->push(new ArrayData(array(
'Title' => _t('TableListField.CSVEXPORT', 'Export to CSV'),
'Link' => $this->ExportLink()
)));
}
if($this->can('print')) {
$links->push(new ArrayData(array(
'Title' => _t('TableListField.PRINT', 'Print'),
'Link' => $this->PrintLink()
)));
}
return $links;
}
public function setFieldCasting($casting) {
$this->fieldCasting = $casting;
return $this;
}
public function setFieldFormatting($formatting) {
$this->fieldFormatting = $formatting;
return $this;
}
public function setCSVFieldFormatting($formatting) {
$this->csvFieldFormatting = $formatting;
return $this;
}
/**
* Edit the field list
*/
public function setFieldList($fieldList) {
$this->fieldList = $fieldList;
return $this;
}
/**
* @return String
*/
public function Name() {
return $this->name;
}
public function Title() {
// adding translating functionality
// this is a bit complicated, because this parameter is passed to this class
// and should come here translated already
// adding this to TODO probably add a method to the classes
// to return they're translated string
// added by ruibarreiros @ 27/11/2007
return $this->sourceClass() ? singleton($this->sourceClass())->singular_name() : $this->getName();
}
public function NameSingular() {
// same as Title()
// added by ruibarreiros @ 27/11/2007
return $this->sourceClass() ? singleton($this->sourceClass())->singular_name() : $this->getName();
}
public function NamePlural() {
// same as Title()
// added by ruibarreiros @ 27/11/2007
return $this->sourceClass() ? singleton($this->sourceClass())->plural_name() : $this->getName();
}
public function setTemplate($template) {
$this->template = $template;
return $this;
}
public function CurrentLink() {
$link = $this->Link();
if(isset($_REQUEST['ctf'][$this->getName()]['start'])
&& is_numeric($_REQUEST['ctf'][$this->getName()]['start'])) {
$start = ($_REQUEST['ctf'][$this->getName()]['start'] < 0)
? 0
: $_REQUEST['ctf'][$this->getName()]['start'];
$link = Controller::join_links($link, "?ctf[{$this->getName()}][start]={$start}");
}
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
return $link;
}
/**
* Overloaded to automatically add security token.
*
* @param String $action
* @return String
*/
public function Link($action = null) {
$form = $this->getForm();
if($form) {
$token = $form->getSecurityToken();
$parentUrlParts = parse_url(parent::Link($action));
$queryPart = (isset($parentUrlParts['query'])) ? '?' . $parentUrlParts['query'] : null;
// Ensure that URL actions not routed through Form->httpSubmission() are protected against CSRF attacks.
if($token->isEnabled()) $queryPart = $token->addtoUrl($queryPart);
return Controller::join_links($parentUrlParts['path'], $queryPart);
} else {
// allow for instanciation of this FormField outside of a controller/form
// context (e.g. for unit tests)
return false;
}
}
public function BaseLink() {
user_error("TableListField::BaseLink() deprecated, use Link() instead", E_USER_NOTICE);
return $this->Link();
}
/**
* Helper method to determine permissions for a scaffolded
* TableListField (or subclasses) - currently used in {@link ModelAdmin} and
* {@link DataObject->scaffoldFormFields()}.
*
* Returns true for each permission that doesn't have an explicit getter.
*
* @todo Temporary method, implement directly in FormField subclasses with object-level permissions.
*
* @param string $class
* @param numeric $id
* @return array
*/
public static function permissions_for_object($class, $id = null) {
$permissions = array();
$obj = ($id) ? DataObject::get_by_id($class, $id) : singleton($class);
if(!$obj->hasMethod('canView') || $obj->canView()) $permissions[] = 'show';
if(!$obj->hasMethod('canEdit') || $obj->canEdit()) $permissions[] = 'edit';
if(!$obj->hasMethod('canDelete') || $obj->canDelete()) $permissions[] = 'delete';
if(!$obj->hasMethod('canCreate') || $obj->canCreate()) $permissions[] = 'add';
return $permissions;
}
/**
* @param $value
*
*/
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;
$castingField = DBField::create_field($castingFieldType, $value);
$value = call_user_func_array(array($castingField,'XML'),$castingParams);
} else {
$fieldTypeParts = explode('->', $castingDefinition);
$castingFieldType = $fieldTypeParts[0];
$castingMethod = $fieldTypeParts[1];
$castingField = DBField::create_field($castingFieldType, $value);
$value = call_user_func_array(array($castingField,$castingMethod),$castingParams);
}
return $value;
}
public function setHighlightConditions($conditions) {
$this->highlightConditions = $conditions;
return $this;
}
/**
* See {@link SelectOptions()} for introduction.
*
* @param $options array Options to add, key being a unique identifier of the action,
* and value a title for the rendered link element (can contain HTML).
* The keys for 'all' and 'none' have special behaviour associated
* through TableListField.js JavaScript.
* For any other key, the JavaScript automatically checks all checkboxes contained in
* <td> elements with a matching classname.
*/
public function addSelectOptions($options){
foreach($options as $k => $title)
$this->selectOptions[$k] = $title;
}
/**
* Remove one all more table's {@link $selectOptions}
*
* @param $optionsNames array
*/
public function removeSelectOptions($names){
foreach($names as $name){
unset($this->selectOptions[trim($name)]);
}
}
/**
* Return the table's {@link $selectOptions}.
* Used to toggle checkboxes for each table row through button elements.
*
* Requires {@link Markable()} to return TRUE.
* This is only functional with JavaScript enabled.
*
* @return SS_List of ArrayData objects
*/
public function SelectOptions(){
if(!$this->selectOptions) return;
$selectOptionsSet = new ArrayList();
foreach($this->selectOptions as $k => $v) {
$selectOptionsSet->push(new ArrayData(array(
'Key' => $k,
'Value' => $v
)));
}
return $selectOptionsSet;
}
}
/**
* A single record in a TableListField.
* @package forms
* @subpackage fields-relational
* @see TableListField
*/
class TableListField_Item extends ViewableData {
/**
* @var DataObject The underlying data record,
* usually an element of {@link TableListField->sourceItems()}.
*/
protected $item;
/**
* @var TableListField
*/
protected $parent;
public function __construct($item, $parent) {
$this->failover = $this->item = $item;
$this->parent = $parent;
parent::__construct();
}
public function ID() {
return $this->item->ID;
}
public function Parent() {
return $this->parent;
}
public function Fields($xmlSafe = true) {
$list = $this->parent->FieldList();
foreach($list as $fieldName => $fieldTitle) {
$value = "";
// This supports simple FieldName syntax
if(strpos($fieldName,'.') === false) {
$value = ($this->item->XML_val($fieldName) && $xmlSafe)
? $this->item->XML_val($fieldName)
: $this->item->RAW_val($fieldName);
// This support the syntax fieldName = Relation.RelatedField
} else {
$fieldNameParts = explode('.', $fieldName) ;
$tmpItem = $this->item;
for($j=0;$j<sizeof($fieldNameParts);$j++) {
$relationMethod = $fieldNameParts[$j];
$idField = $relationMethod . 'ID';
if($j == sizeof($fieldNameParts)-1) {
if($tmpItem) $value = $tmpItem->$relationMethod;
} else {
if($tmpItem) $tmpItem = $tmpItem->$relationMethod();
}
}
}
// casting
if(array_key_exists($fieldName, $this->parent->fieldCasting)) {
$value = $this->parent->getCastedValue($value, $this->parent->fieldCasting[$fieldName]);
} elseif(is_object($value) && method_exists($value, 'Nice')) {
$value = $value->Nice();
}
// formatting
$item = $this->item;
if(array_key_exists($fieldName, $this->parent->fieldFormatting)) {
$format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$fieldName]);
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
$format = str_replace('__VAL__', '$value', $format);
eval('$value = "' . $format . '";');
}
//escape
if($escape = $this->parent->fieldEscape){
foreach($escape as $search => $replace){
$value = str_replace($search, $replace, $value);
}
}
$fields[] = new ArrayData(array(
"Name" => $fieldName,
"Title" => $fieldTitle,
"Value" => $value,
"CsvSeparator" => $this->parent->getCsvSeparator(),
));
}
return new ArrayList($fields);
}
public function Markable() {
return $this->parent->Markable;
}
/**
* Checks global permissions for field in {@link TableListField->Can()}.
* If they are allowed, it checks for object permissions by assuming
* a method with "can" + $mode parameter naming, e.g. canDelete().
*
* @param string $mode See {@link TableListField::$permissions} array.
* @return boolean
*/
public function Can($mode) {
$canMethod = "can" . ucfirst($mode);
if(!$this->parent->Can($mode)) {
// check global settings for the field instance
return false;
} elseif($this->item->hasMethod($canMethod)) {
// if global allows, check object specific permissions (e.g. canDelete())
return $this->item->$canMethod();
} else {
// otherwise global allowed this action, so return TRUE
return true;
}
}
public function Link($action = null) {
$form = $this->parent->getForm();
if($form) {
$token = $form->getSecurityToken();
$parentUrlParts = parse_url($this->parent->Link());
$queryPart = (isset($parentUrlParts['query'])) ? '?' . $parentUrlParts['query'] : null;
// Ensure that URL actions not routed through Form->httpSubmission() are protected against CSRF attacks.
if($token->isEnabled()) $queryPart = $token->addtoUrl($queryPart);
return Controller::join_links($parentUrlParts['path'], 'item', $this->item->ID, $action, $queryPart);
} else {
// allow for instanciation of this FormField outside of a controller/form
// context (e.g. for unit tests)
return false;
}
}
/**
* Returns all row-based actions not disallowed through permissions.
* See TableListField->Action for a similiar dummy-function to work
* around template-inheritance issues.
*
* @return SS_List
*/
public function Actions() {
$allowedActions = new ArrayList();
foreach($this->parent->actions as $actionName => $actionSettings) {
if($this->parent->Can($actionName)) {
$allowedActions->push(new ArrayData(array(
'Name' => $actionName,
'Link' => $this->{ucfirst($actionName).'Link'}(),
'Icon' => $actionSettings['icon'],
'IconDisabled' => $actionSettings['icon_disabled'],
'Label' => $actionSettings['label'],
'Class' => $actionSettings['class'],
'Default' => ($actionName == $this->parent->defaultAction),
'IsAllowed' => $this->Can($actionName),
)));
}
}
return $allowedActions;
}
public function BaseLink() {
user_error("TableListField_Item::BaseLink() deprecated, use Link() instead", E_USER_NOTICE);
return $this->Link();
}
public function DeleteLink() {
return Controller::join_links($this->Link(), "delete");
}
public function MarkingCheckbox() {
$name = $this->parent->getName() . '[]';
if($this->parent->isReadonly())
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\""
. " disabled=\"disabled\" />";
else
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\" />";
}
/**
* According to {@link TableListField->selectOptions}, each record will check if the options' key on the object is
* true, if it is true, add the key as a class to the record
*
* @return string Value for a 'class' HTML attribute.
*/
public function SelectOptionClasses(){
$tagArray = array('markingcheckbox');
$options = $this->parent->SelectOptions();
if($options && $options->exists()){
foreach($options as $option){
if($option->Key !== 'all' && $option->Key !== 'none'){
if($this->{$option->Key}) {
$tagArray[] = $option->Key;
}
}
}
}
return implode(" ",$tagArray);
}
public function HighlightClasses() {
$classes = array();
foreach($this->parent->highlightConditions as $condition) {
$rule = str_replace("\$","\$this->item->", $condition['rule']);
$ruleApplies = null;
eval('$ruleApplies = ('.$rule.');');
if($ruleApplies) {
if(isset($condition['exclusive']) && $condition['exclusive']) {
return $condition['class'];
} else {
$classes[] = $condition['class'];
}
}
}
return (count($classes) > 0) ? " " . implode(" ", $classes) : false;
}
/**
* Legacy: Please use permissions instead
*/
public function isReadonly() {
return $this->parent->Can('delete');
}
}
/**
* @package forms
* @subpackage fields-relational
*/
class TableListField_ItemRequest extends RequestHandler {
protected $ctf;
protected $itemID;
protected $methodName;
static $url_handlers = array(
'$Action!' => '$Action',
'' => 'index',
);
public function Link() {
return Controller::join_links($this->ctf->Link(), 'item/' . $this->itemID);
}
public function __construct($ctf, $itemID) {
$this->ctf = $ctf;
$this->itemID = $itemID;
parent::__construct();
}
public function delete($request) {
// Protect against CSRF on destructive action
$token = $this->ctf->getForm()->getSecurityToken();
if(!$token->checkRequest($request)) return $this->httpError('400');
if($this->ctf->Can('delete') !== true) {
return false;
}
$this->dataObj()->delete();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Return the data object being manipulated
*/
public function dataObj() {
// used to discover fields if requested and for population of field
if(is_numeric($this->itemID)) {
// we have to use the basedataclass, otherwise we might exclude other subclasses
return $this->ctf->getDataList()->byId($this->itemID);
}
}
/**
* @return TableListField
*/
public function getParentController() {
return $this->ctf;
}
}
Jump to Line
Something went wrong with that request. Please try again.