2011-12-06 01:56:24 +01:00
< ? php
2016-06-15 06:03:16 +02:00
2016-08-19 00:51:35 +02:00
namespace SilverStripe\Forms\GridField ;
2018-09-04 01:35:17 +02:00
use LogicException ;
use SilverStripe\Admin\LeftAndMain ;
use SilverStripe\Control\Controller ;
use SilverStripe\Control\HTTPResponse ;
use SilverStripe\Core\Convert ;
use SilverStripe\Dev\Deprecation ;
2016-08-19 00:51:35 +02:00
use SilverStripe\Forms\FieldGroup ;
2018-09-04 01:35:17 +02:00
use SilverStripe\Forms\FieldList ;
use SilverStripe\Forms\Form ;
use SilverStripe\Forms\Schema\FormSchema ;
2016-08-19 00:51:35 +02:00
use SilverStripe\Forms\TextField ;
2018-09-04 01:35:17 +02:00
use SilverStripe\ORM\ArrayList ;
2016-09-09 08:43:05 +02:00
use SilverStripe\ORM\Filterable ;
2016-06-15 06:03:16 +02:00
use SilverStripe\ORM\SS_List ;
2016-08-19 00:51:35 +02:00
use SilverStripe\View\ArrayData ;
use SilverStripe\View\SSViewer ;
2011-12-06 01:56:24 +01:00
/**
2014-08-15 08:53:05 +02:00
* GridFieldFilterHeader alters the { @ link GridField } with some filtering
2013-05-20 12:18:07 +02:00
* fields in the header of each column .
2014-08-15 08:53:05 +02:00
*
2011-12-06 01:56:24 +01:00
* @ see GridField
*/
2018-09-04 01:35:17 +02:00
class GridFieldFilterHeader implements GridField_URLHandler , GridField_HTMLProvider , GridField_DataManipulator , GridField_ActionProvider
2016-11-29 00:31:16 +01:00
{
/**
* See { @ link setThrowExceptionOnBadDataType ()}
*
* @ var bool
*/
protected $throwExceptionOnBadDataType = true ;
2018-09-04 01:35:17 +02:00
/**
* Indicates that this component should revert to displaying it ' s legacy
* table header style rather than the react driven search box
*
* @ deprecated 5.0
* @ var bool
*/
public $useLegacyFilterHeader = false ;
/**
* @ inheritDoc
*/
public function getURLHandlers ( $gridField )
{
return [
'GET schema/SearchForm' => 'getSearchFormSchema'
];
}
/**
* @ param bool $useLegacy
*/
public function __construct ( $useLegacy = false )
{
$this -> useLegacyFilterHeader = $useLegacy ;
}
2016-11-29 00:31:16 +01:00
/**
* Determine what happens when this component is used with a list that isn ' t { @ link SS_Filterable } .
*
* - true : An exception is thrown
* - false : This component will be ignored - it won ' t make any changes to the GridField .
*
* By default , this is set to true so that it 's clearer what' s happening , but the predefined
* { @ link GridFieldConfig } subclasses set this to false for flexibility .
*
* @ param bool $throwExceptionOnBadDataType
*/
public function setThrowExceptionOnBadDataType ( $throwExceptionOnBadDataType )
{
$this -> throwExceptionOnBadDataType = $throwExceptionOnBadDataType ;
}
/**
* See { @ link setThrowExceptionOnBadDataType ()}
*/
public function getThrowExceptionOnBadDataType ()
{
return $this -> throwExceptionOnBadDataType ;
}
/**
* Check that this dataList is of the right data type .
* Returns false if it ' s a bad data type , and if appropriate , throws an exception .
*
* @ param SS_List $dataList
* @ return bool
*/
protected function checkDataType ( $dataList )
{
if ( $dataList instanceof Filterable ) {
return true ;
} else {
if ( $this -> throwExceptionOnBadDataType ) {
throw new LogicException (
2017-05-17 07:40:13 +02:00
static :: class . " expects an SS_Filterable list to be passed to the GridField. "
2016-11-29 00:31:16 +01:00
);
}
return false ;
}
}
/**
2018-09-04 01:35:17 +02:00
* If the GridField has a filterable datalist , return an array of actions
2016-11-29 00:31:16 +01:00
*
* @ param GridField $gridField
* @ return array
*/
public function getActions ( $gridField )
{
if ( ! $this -> checkDataType ( $gridField -> getList ())) {
return [];
}
2018-09-04 01:35:17 +02:00
return [ 'filter' , 'reset' ];
2016-11-29 00:31:16 +01:00
}
2018-09-04 01:35:17 +02:00
/**
* If the GridField has a filterable datalist , return an array of actions
*
* @ param GridField $gridField
* @ return array
*/
2016-11-29 00:31:16 +01:00
public function handleAction ( GridField $gridField , $actionName , $arguments , $data )
{
if ( ! $this -> checkDataType ( $gridField -> getList ())) {
return ;
}
$state = $gridField -> State -> GridFieldFilterHeader ;
if ( $actionName === 'filter' ) {
if ( isset ( $data [ 'filter' ][ $gridField -> getName ()])) {
foreach ( $data [ 'filter' ][ $gridField -> getName ()] as $key => $filter ) {
$state -> Columns -> $key = $filter ;
}
}
} elseif ( $actionName === 'reset' ) {
$state -> Columns = null ;
}
}
/**
2018-09-04 01:35:17 +02:00
* @ inheritDoc
2016-11-29 00:31:16 +01:00
*/
public function getManipulatedData ( GridField $gridField , SS_List $dataList )
{
if ( ! $this -> checkDataType ( $dataList )) {
return $dataList ;
}
/** @var Filterable $dataList */
/** @var GridState_Data $columns */
$columns = $gridField -> State -> GridFieldFilterHeader -> Columns ( null );
if ( empty ( $columns )) {
return $dataList ;
}
$filterArguments = $columns -> toArray ();
$dataListClone = clone ( $dataList );
foreach ( $filterArguments as $columnName => $value ) {
if ( $dataList -> canFilterBy ( $columnName ) && $value ) {
2018-01-16 19:39:30 +01:00
$dataListClone = $dataListClone -> filter ( $columnName . ':PartialMatch' , $value );
2016-11-29 00:31:16 +01:00
}
}
return $dataListClone ;
}
2016-12-13 02:18:10 +01:00
/**
2018-09-04 01:35:17 +02:00
* Returns whether this { @ link GridField } has any columns to filter on at all
2016-12-13 02:18:10 +01:00
*
2017-01-11 00:00:01 +01:00
* @ param GridField $gridField
2016-12-13 02:18:10 +01:00
* @ return boolean
*/
2018-09-24 13:39:33 +02:00
public function canFilterAnyColumns ( $gridField )
2016-12-13 02:18:10 +01:00
{
$list = $gridField -> getList ();
if ( ! $this -> checkDataType ( $list )) {
return false ;
}
$columns = $gridField -> getColumns ();
foreach ( $columns as $columnField ) {
$metadata = $gridField -> getColumnMetadata ( $columnField );
$title = $metadata [ 'title' ];
if ( $title && $list -> canFilterBy ( $columnField )) {
return true ;
}
}
return false ;
}
2018-09-04 01:35:17 +02:00
/**
* Generate a search context based on the model class of the of the GridField
*
* @ param GridField $gridfield
* @ return \SilverStripe\ORM\Search\SearchContext
*/
public function getSearchContext ( GridField $gridField )
2016-11-29 00:31:16 +01:00
{
2018-09-04 01:35:17 +02:00
$context = singleton ( $gridField -> getModelClass ()) -> getDefaultSearchContext ();
return $context ;
}
/**
* Returns the search field schema for the component
*
* @ param GridField $gridfield
* @ return string
*/
public function getSearchFieldSchema ( GridField $gridField )
{
$schemaUrl = Controller :: join_links ( $gridField -> Link (), 'schema/SearchForm' );
$context = $this -> getSearchContext ( $gridField );
$params = $gridField -> getRequest () -> postVar ( 'filter' ) ? : [];
if ( array_key_exists ( $gridField -> getName (), $params )) {
$params = $params [ $gridField -> getName ()];
}
$context -> setSearchParams ( $params );
$searchField = $context -> getSearchFields () -> first ();
$searchField = $searchField && property_exists ( $searchField , 'name' ) ? $searchField -> name : null ;
$name = $gridField -> Title ? : singleton ( $gridField -> getModelClass ()) -> i18n_plural_name ();
$schema = [
'formSchemaUrl' => $schemaUrl ,
'name' => $searchField ,
'placeholder' => _t ( __CLASS__ . '.Search' , 'Search "{name}"' , [ 'name' => $name ]),
'filters' => $context -> getSearchParams () ? : new \stdClass , // stdClass maps to empty json object '{}'
'gridfield' => $gridField -> getName (),
'searchAction' => GridField_FormAction :: create ( $gridField , 'filter' , false , 'filter' , null ) -> getAttribute ( 'name' ),
'clearAction' => GridField_FormAction :: create ( $gridField , 'reset' , false , 'reset' , null ) -> getAttribute ( 'name' )
];
return Convert :: raw2json ( $schema );
}
/**
* Returns the search form schema for the component
*
* @ param GridField $gridfield
* @ return HTTPResponse
*/
public function getSearchFormSchema ( GridField $gridField )
{
$searchContext = $this -> getSearchContext ( $gridField );
$searchFields = $searchContext -> getSearchFields ();
// If there are no filterable fields, return a 400 response
if ( $searchFields -> count () === 0 ) {
return new HTTPResponse ( _t ( __CLASS__ . '.SearchFormFaliure' , 'No search form could be generated' ), 400 );
}
2018-09-27 07:39:50 +02:00
// Append a prefix to search field names to prevent conflicts with other fields in the search form
foreach ( $searchFields as $field ) {
$field -> setName ( 'Search__' . $field -> getName ());
}
2018-09-04 01:35:17 +02:00
$columns = $gridField -> getColumns ();
// Update field titles to match column titles
foreach ( $columns as $columnField ) {
$metadata = $gridField -> getColumnMetadata ( $columnField );
// Get the field name, without any modifications
$name = explode ( '.' , $columnField );
$title = $metadata [ 'title' ];
$field = $searchFields -> fieldByName ( $name [ 0 ]);
if ( $field ) {
$field -> setTitle ( $title );
}
}
foreach ( $searchFields -> getIterator () as $field ) {
$field -> addExtraClass ( 'stacked' );
}
$form = new Form (
$gridField ,
" SearchForm " ,
$searchFields ,
new FieldList ()
);
$form -> setFormMethod ( 'get' );
$form -> setFormAction ( $gridField -> Link ());
$form -> addExtraClass ( 'cms-search-form form--no-dividers' );
$form -> disableSecurityToken (); // This form is not tied to session so we disable this
$form -> loadDataFrom ( $gridField -> getRequest () -> getVars ());
$parts = $gridField -> getRequest () -> getHeader ( LeftAndMain :: SCHEMA_HEADER );
$schemaID = $gridField -> getRequest () -> getURL ();
$data = FormSchema :: singleton ()
-> getMultipartSchema ( $parts , $schemaID , $form );
$response = new HTTPResponse ( Convert :: raw2json ( $data ));
$response -> addHeader ( 'Content-Type' , 'application/json' );
return $response ;
}
/**
* Generate fields for the legacy filter header row
*
* @ deprecated 5.0
* @ param GridField $gridfield
* @ return ArrayList | null
*/
public function getLegacyFilterHeader ( GridField $gridField )
{
Deprecation :: notice ( '5.0' , 'Table row based filter header will be removed in favor of search field in 5.0' );
2016-11-29 00:31:16 +01:00
$list = $gridField -> getList ();
if ( ! $this -> checkDataType ( $list )) {
return null ;
}
$columns = $gridField -> getColumns ();
$filterArguments = $gridField -> State -> GridFieldFilterHeader -> Columns -> toArray ();
$currentColumn = 0 ;
2016-12-13 02:18:10 +01:00
$canFilter = false ;
2018-09-04 01:35:17 +02:00
$fieldsList = new ArrayList ();
2016-12-13 02:18:10 +01:00
2016-11-29 00:31:16 +01:00
foreach ( $columns as $columnField ) {
$currentColumn ++ ;
$metadata = $gridField -> getColumnMetadata ( $columnField );
$title = $metadata [ 'title' ];
$fields = new FieldGroup ();
if ( $title && $list -> canFilterBy ( $columnField )) {
2016-12-13 02:18:10 +01:00
$canFilter = true ;
2016-11-29 00:31:16 +01:00
$value = '' ;
if ( isset ( $filterArguments [ $columnField ])) {
$value = $filterArguments [ $columnField ];
}
$field = new TextField ( 'filter[' . $gridField -> getName () . '][' . $columnField . ']' , '' , $value );
$field -> addExtraClass ( 'grid-field__sort-field' );
$field -> addExtraClass ( 'no-change-track' );
$field -> setAttribute (
'placeholder' ,
2018-01-16 19:39:30 +01:00
_t ( 'SilverStripe\\Forms\\GridField\\GridField.FilterBy' , " Filter by " ) . _t ( 'SilverStripe\\Forms\\GridField\\GridField.' . $metadata [ 'title' ], $metadata [ 'title' ])
2016-11-29 00:31:16 +01:00
);
$fields -> push ( $field );
$fields -> push (
GridField_FormAction :: create ( $gridField , 'reset' , false , 'reset' , null )
2016-12-13 02:18:10 +01:00
-> addExtraClass ( 'btn font-icon-cancel btn-secondary btn--no-text ss-gridfield-button-reset' )
2017-04-20 03:15:24 +02:00
-> setAttribute ( 'title' , _t ( 'SilverStripe\\Forms\\GridField\\GridField.ResetFilter' , " Reset " ))
2016-11-29 00:31:16 +01:00
-> setAttribute ( 'id' , 'action_reset_' . $gridField -> getModelClass () . '_' . $columnField )
);
}
if ( $currentColumn == count ( $columns )) {
$fields -> push (
GridField_FormAction :: create ( $gridField , 'filter' , false , 'filter' , null )
-> addExtraClass ( 'btn font-icon-search btn--no-text btn--icon-large grid-field__filter-submit ss-gridfield-button-filter' )
2017-04-20 03:15:24 +02:00
-> setAttribute ( 'title' , _t ( 'SilverStripe\\Forms\\GridField\\GridField.Filter' , 'Filter' ))
2016-11-29 00:31:16 +01:00
-> setAttribute ( 'id' , 'action_filter_' . $gridField -> getModelClass () . '_' . $columnField )
);
$fields -> push (
GridField_FormAction :: create ( $gridField , 'reset' , false , 'reset' , null )
2016-12-20 22:43:33 +01:00
-> addExtraClass ( 'btn font-icon-cancel btn--no-text grid-field__filter-clear btn--icon-md ss-gridfield-button-close' )
2017-04-20 03:15:24 +02:00
-> setAttribute ( 'title' , _t ( 'SilverStripe\\Forms\\GridField\\GridField.ResetFilter' , " Reset " ))
2016-11-29 00:31:16 +01:00
-> setAttribute ( 'id' , 'action_reset_' . $gridField -> getModelClass () . '_' . $columnField )
);
2017-04-21 05:30:09 +02:00
$fields -> addExtraClass ( 'grid-field__filter-buttons' );
2016-11-29 00:31:16 +01:00
$fields -> addExtraClass ( 'no-change-track' );
}
2018-09-04 01:35:17 +02:00
$fieldsList -> push ( $fields );
2016-11-29 00:31:16 +01:00
}
2018-09-04 01:35:17 +02:00
return $canFilter ? $fieldsList : null ;
}
/**
* Either returns the legacy filter header or the search button and field
*
* @ param GridField $gridField
* @ return array | null
*/
public function getHTMLFragments ( $gridField )
{
$forTemplate = new ArrayData ([]);
if ( ! $this -> canFilterAnyColumns ( $gridField )) {
2016-12-13 02:18:10 +01:00
return null ;
}
2018-09-04 01:35:17 +02:00
if ( $this -> useLegacyFilterHeader ) {
$fieldsList = $this -> getLegacyFilterHeader ( $gridField );
$forTemplate -> Fields = $fieldsList ;
$filterTemplates = SSViewer :: get_templates_by_class ( $this , '_Row' , __CLASS__ );
return [ 'header' => $forTemplate -> renderWith ( $filterTemplates )];
} else {
$fieldSchema = $this -> getSearchFieldSchema ( $gridField );
$forTemplate -> SearchFieldSchema = $fieldSchema ;
$searchTemplates = SSViewer :: get_templates_by_class ( $this , '_Search' , __CLASS__ );
return [
'before' => $forTemplate -> renderWith ( $searchTemplates ),
'buttons-before-right' => sprintf (
'<button type="button" name="showFilter" aria-label="%s" title="%s"' .
' class="btn btn-secondary font-icon-search btn--no-text btn--icon-large grid-field__filter-open"></button>' ,
_t ( 'SilverStripe\\Forms\\GridField\\GridField.OpenFilter' , " Open search and filter " ),
_t ( 'SilverStripe\\Forms\\GridField\\GridField.OpenFilter' , " Open search and filter " )
)
];
}
2016-11-29 00:31:16 +01:00
}
2012-02-11 03:26:26 +01:00
}