2008-08-06 05:28:25 +02:00
< ? php
/**
2008-08-09 04:00:40 +02:00
* Generates a three - pane UI for editing model classes ,
2008-08-06 05:28:25 +02:00
* with an automatically generated search panel , tabular results
* and edit forms .
* Relies on data such as { @ link DataObject :: $db } and { @ DataObject :: getCMSFields ()}
* to scaffold interfaces " out of the box " , while at the same time providing
* flexibility to customize the default output .
*
2008-08-09 04:00:40 +02:00
* Add a route ( note - this doc is not currently in sync with the code , need to update )
2008-08-06 05:28:25 +02:00
* < code >
* Director :: addRules ( 50 , array ( 'admin/mymodel/$Class/$Action/$ID' => 'MyModelAdmin' ));
* </ code >
*
* @ todo saving logic ( should mostly use Form -> saveInto () and iterate over relations )
* @ todo ajax form loading and saving
* @ todo ajax result display
* @ todo relation formfield scaffolding ( one tab per relation ) - relations don ' t have DBField sublclasses , we do
* we define the scaffold defaults . can be ComplexTableField instances for a start .
* @ todo has_many / many_many relation autocomplete field ( HasManyComplexTableField doesn ' t work well with larger datasets )
*
* Long term TODOs :
* @ todo Hook into RESTful interface on DataObjects ( yet to be developed )
* @ todo Permission control via datamodel and Form class
*
2008-09-26 06:21:15 +02:00
* @ uses SearchContext
2008-08-06 05:28:25 +02:00
*
* @ package cms
2009-03-22 23:58:18 +01:00
* @ subpackage core
2008-08-06 05:28:25 +02:00
*/
abstract class ModelAdmin extends LeftAndMain {
2008-11-02 22:27:55 +01:00
static $url_rule = '/$Action' ;
2008-08-06 05:28:25 +02:00
/**
* List of all managed { @ link DataObject } s in this interface .
*
2009-05-18 23:13:28 +02:00
* Simple notation with class names only :
* < code >
* array ( 'MyObjectClass' , 'MyOtherObjectClass' )
* </ code >
*
* Extended notation with options ( e . g . custom titles ) :
* < code >
* array (
* 'MyObjectClass' => array ( 'title' => " Custom title " )
* )
* </ code >
*
* Available options :
* - 'title' : Set custom titles for the tabs or dropdown names
2009-05-20 12:03:11 +02:00
* - 'collection_controller' : Set a custom class to use as a collection controller for this model
* - 'record_controller' : Set a custom class to use as a record controller for this model
2009-05-18 23:13:28 +02:00
*
2008-11-09 15:34:05 +01:00
* @ var array | string
2008-08-06 05:28:25 +02:00
*/
2009-05-06 08:37:45 +02:00
public static $managed_models = null ;
2008-08-06 05:28:25 +02:00
2008-11-04 02:32:45 +01:00
/**
* More actions are dynamically added in { @ link defineMethods ()} below .
*/
2008-08-06 05:28:25 +02:00
public static $allowed_actions = array (
'add' ,
'edit' ,
2008-08-09 05:54:55 +02:00
'delete' ,
2008-08-09 07:00:42 +02:00
'import' ,
'renderimportform' ,
2008-08-09 05:54:55 +02:00
'handleList' ,
2008-08-11 02:03:57 +02:00
'handleItem' ,
'ImportForm'
2008-08-09 05:54:55 +02:00
);
2008-08-09 09:03:24 +02:00
/**
* @ param string $collection_controller_class Override for controller class
*/
2009-05-06 08:37:45 +02:00
public static $collection_controller_class = " ModelAdmin_CollectionController " ;
2008-08-09 09:03:24 +02:00
/**
* @ param string $collection_controller_class Override for controller class
*/
2009-05-06 08:37:45 +02:00
public static $record_controller_class = " ModelAdmin_RecordController " ;
2008-08-09 09:03:24 +02:00
2008-08-09 05:54:55 +02:00
/**
* Forward control to the default action handler
*/
public static $url_handlers = array (
'$Action' => 'handleAction'
2008-08-06 05:28:25 +02:00
);
2008-08-09 04:16:46 +02:00
/**
* Model object currently in manipulation queue . Used for updating Link to point
* to the correct generic data object in generated URLs .
*
* @ var string
*/
private $currentModel = false ;
2008-08-09 05:54:55 +02:00
2008-08-09 07:00:42 +02:00
/**
* List of all { @ link DataObject } s which can be imported through
* a subclass of { @ link BulkLoader } ( mostly CSV data ) .
* By default { @ link CsvBulkLoader } is used , assuming a standard mapping
* of column names to { @ link DataObject } properties / relations .
2008-12-04 23:38:58 +01:00
*
* e . g . " BlogEntry " => " BlogEntryCsvBulkLoader "
2008-08-09 07:00:42 +02:00
*
* @ var array
*/
2009-05-06 08:37:45 +02:00
public static $model_importers = null ;
2008-08-09 07:00:42 +02:00
/**
* Amount of results showing on a single page .
*
* @ var int
*/
2009-05-06 08:37:45 +02:00
public static $page_length = 30 ;
2008-08-09 07:00:42 +02:00
2008-12-04 23:38:58 +01:00
/**
* Class name of the form field used for the results list . Overloading this in subclasses
* can let you customise the results table field .
*/
protected $resultsTableClassName = 'TableListField' ;
/**
* Return { @ link $this -> resultsTableClassName }
*/
public function resultsTableClassName () {
return $this -> resultsTableClassName ;
}
2008-08-09 04:16:46 +02:00
/**
2008-08-09 05:54:55 +02:00
* Initialize the model admin interface . Sets up embedded jquery libraries and requisite plugins .
*
* @ todo remove reliance on urlParams
2008-08-09 04:16:46 +02:00
*/
2008-08-06 05:28:25 +02:00
public function init () {
parent :: init ();
// security check for valid models
2008-08-09 05:54:55 +02:00
if ( isset ( $this -> urlParams [ 'Action' ]) && ! in_array ( $this -> urlParams [ 'Action' ], $this -> getManagedModels ())) {
2008-08-09 07:00:42 +02:00
//user_error('ModelAdmin::init(): Invalid Model class', E_USER_ERROR);
2008-08-06 05:28:25 +02:00
}
2008-09-29 19:26:02 +02:00
Requirements :: css ( CMS_DIR . '/css/ModelAdmin.css' ); // standard layout formatting for management UI
Requirements :: css ( CMS_DIR . '/css/silverstripe.tabs.css' ); // follows the jQuery UI theme conventions
2008-08-09 04:16:46 +02:00
2008-09-29 19:26:02 +02:00
Requirements :: javascript ( THIRDPARTY_DIR . '/jquery/jquery.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/jquery/plugins/livequery/jquery.livequery.js' );
2009-04-29 03:44:28 +02:00
//Requirements::javascript(THIRDPARTY_DIR . '/jquery/ui/ui.core.js');
//Requirements::javascript(THIRDPARTY_DIR . '/jquery/ui/ui.tabs.js');
2008-09-29 19:26:02 +02:00
Requirements :: javascript ( THIRDPARTY_DIR . '/jquery/plugins/form/jquery.form.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/jquery/plugins/effen/jquery.fn.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/jquery/jquery_improvements.js' );
ENHANCEMENT Introduced constants for system paths like /sapphire in preparation for a more flexible directory reorganisation. Instead of hardcoding your path, please use the following constants: BASE_PATH, BASE_URL, SAPPHIRE_DIR, SAPPHIRE_PATH, CMS_DIR, CMS_PATH, THIRDPARTY_DIR, THIRDPARTY_PATH, ASSETS_DIR, ASSETS_PATH, THEMES_DIR, THEMES_PATH
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@63175 467b73ca-7a2a-4603-9d3b-597d59a354a9
2008-09-28 15:12:20 +02:00
Requirements :: javascript ( CMS_DIR . '/javascript/ModelAdmin.js' );
2008-08-06 05:28:25 +02:00
}
2008-08-11 01:17:51 +02:00
/**
* overwrite the static page_length of the admin panel ,
* should be called in the project _config file .
*/
static function set_page_length ( $length ){
self :: $page_length = $length ;
}
/**
* Return the static page_length of the admin , default as 30
*/
static function get_page_length (){
return self :: $page_length ;
}
2008-08-09 04:16:46 +02:00
/**
* Add mappings for generic form constructors to automatically delegate to a scaffolded form object .
*/
function defineMethods () {
parent :: defineMethods ();
2009-05-18 23:13:28 +02:00
foreach ( $this -> getManagedModels () as $class => $options ) {
if ( is_numeric ( $class )) $class = $options ;
$this -> addWrapperMethod ( $class , 'bindModelController' );
self :: $allowed_actions [] = $class ;
2008-08-09 04:16:46 +02:00
}
2008-08-09 05:54:55 +02:00
}
2008-08-09 04:16:46 +02:00
2008-08-06 05:28:25 +02:00
/**
2008-08-09 05:54:55 +02:00
* Base scaffolding method for returning a generic model instance .
2008-08-06 05:28:25 +02:00
*/
2008-08-09 05:54:55 +02:00
public function bindModelController ( $model , $request = null ) {
2009-05-20 12:03:11 +02:00
$models = $this -> getManagedModels ();
if ( isset ( $models [ $model ][ 'collection_controller' ])) {
$class = $models [ $model ][ 'collection_controller' ];
} else {
$class = $this -> stat ( 'collection_controller_class' );
}
2008-08-09 09:03:24 +02:00
return new $class ( $this , $model );
2008-08-06 05:28:25 +02:00
}
2008-09-13 06:45:30 +02:00
/**
* This method can be overloaded to specify the UI by which the search class is chosen .
*
* It can create a tab strip or a dropdown . The dropdown is useful when there are a large number of classes .
* By default , it will show a tabs for 1 - 3 classes , and a dropdown for 4 or more classes .
*
* @ return String : 'tabs' or 'dropdown'
*/
public function SearchClassSelector () {
return sizeof ( $this -> getManagedModels ()) > 3 ? 'dropdown' : 'tabs' ;
}
2008-08-06 05:28:25 +02:00
/**
2008-11-05 09:55:07 +01:00
* Returns managed models ' create , search , and import forms
2008-09-26 06:22:39 +02:00
* @ uses SearchContext
* @ uses SearchFilter
2008-11-05 09:55:07 +01:00
* @ return DataObjectSet of forms
2008-08-06 05:28:25 +02:00
*/
2008-11-05 09:55:07 +01:00
protected function getModelForms () {
2009-05-16 07:58:54 +02:00
$models = $this -> getManagedModels ();
$forms = new DataObjectSet ();
2008-08-06 05:28:25 +02:00
2009-05-18 23:13:28 +02:00
foreach ( $models as $class => $options ) {
if ( is_numeric ( $class )) $class = $options ;
2009-05-16 07:58:54 +02:00
$forms -> push ( new ArrayData ( array (
2009-08-26 22:54:24 +02:00
'Title' => ( is_array ( $options ) && isset ( $options [ 'title' ])) ? $options [ 'title' ] : singleton ( $class ) -> i18n_singular_name (),
2009-05-16 07:58:54 +02:00
'ClassName' => $class ,
'Content' => $this -> $class () -> getModelSidebar ()
2008-08-06 05:28:25 +02:00
)));
}
return $forms ;
}
/**
* @ return array
*/
2009-02-03 04:22:20 +01:00
function getManagedModels () {
2008-08-06 05:28:25 +02:00
$models = $this -> stat ( 'managed_models' );
2009-02-03 21:34:13 +01:00
if ( is_string ( $models )) {
$models = array ( $models );
}
if ( ! count ( $models )) {
user_error (
' ModelAdmin :: getManagedModels () :
2009-05-16 08:08:27 +02:00
You need to specify at least one DataObject subclass in public static $managed_models .
Make sure that this property is defined , and that its visibility is set to " public " ' ,
2009-02-03 21:34:13 +01:00
E_USER_ERROR
);
}
2008-08-06 05:28:25 +02:00
return $models ;
}
2008-08-09 07:00:42 +02:00
/**
* Returns all importers defined in { @ link self :: $model_importers } .
* If none are defined , we fall back to { @ link self :: managed_models }
* with a default { @ link CsvBulkLoader } class . In this case the column names of the first row
* in the CSV file are assumed to have direct mappings to properties on the object .
*
* @ return array
*/
2008-11-05 09:55:07 +01:00
function getModelImporters () {
2008-08-09 07:00:42 +02:00
$importers = $this -> stat ( 'model_importers' );
2008-08-11 01:29:30 +02:00
2008-08-09 07:00:42 +02:00
// fallback to all defined models if not explicitly defined
2008-08-11 01:29:30 +02:00
if ( is_null ( $importers )) {
2008-08-09 07:00:42 +02:00
$models = $this -> getManagedModels ();
2009-05-18 23:13:28 +02:00
foreach ( $models as $modelName => $options ) {
if ( is_numeric ( $modelName )) $modelName = $options ;
$importers [ $modelName ] = 'CsvBulkLoader' ;
}
2008-08-09 07:00:42 +02:00
}
return $importers ;
}
2008-11-05 09:55:07 +01:00
2008-08-09 05:54:55 +02:00
}
/**
* Handles a managed model class and provides default collection filtering behavior .
*
2009-03-22 23:58:18 +01:00
* @ package cms
* @ subpackage core
2008-08-09 05:54:55 +02:00
*/
class ModelAdmin_CollectionController extends Controller {
2008-08-11 02:21:44 +02:00
public $parentController ;
2008-08-09 05:54:55 +02:00
protected $modelClass ;
static $url_handlers = array (
'$Action' => 'handleActionOrID'
);
function __construct ( $parent , $model ) {
$this -> parentController = $parent ;
$this -> modelClass = $model ;
2008-11-04 02:32:45 +01:00
parent :: __construct ();
2008-08-09 05:54:55 +02:00
}
2008-08-06 05:28:25 +02:00
/**
2008-08-09 05:54:55 +02:00
* Appends the model class to the URL .
*
* @ return unknown
2008-08-06 05:28:25 +02:00
*/
2008-08-09 05:54:55 +02:00
function Link () {
return Controller :: join_links ( $this -> parentController -> Link (), " $this->modelClass " );
2008-08-09 04:16:46 +02:00
}
/**
2008-08-09 05:54:55 +02:00
* Return the class name of the model being managed .
2008-08-09 04:16:46 +02:00
*
2008-08-09 05:54:55 +02:00
* @ return unknown
2008-08-09 04:16:46 +02:00
*/
2008-08-09 05:54:55 +02:00
function getModelClass () {
return $this -> modelClass ;
2008-08-06 05:28:25 +02:00
}
2008-08-09 06:06:52 +02:00
2008-08-06 05:28:25 +02:00
/**
2008-08-09 05:54:55 +02:00
* Delegate to different control flow , depending on whether the
2008-08-09 06:53:34 +02:00
* URL parameter is a number ( record id ) or string ( action ) .
2008-08-06 05:28:25 +02:00
*
2008-08-09 05:54:55 +02:00
* @ param unknown_type $request
* @ return unknown
2008-08-06 05:28:25 +02:00
*/
2008-08-09 05:54:55 +02:00
function handleActionOrID ( $request ) {
if ( is_numeric ( $request -> param ( 'Action' ))) {
return $this -> handleID ( $request );
} else {
return $this -> handleAction ( $request );
}
2008-08-06 05:28:25 +02:00
}
2008-08-09 05:54:55 +02:00
/**
* Delegate to the RecordController if a valid numeric ID appears in the URL
* segment .
*
* @ param HTTPRequest $request
* @ return RecordController
*/
2009-05-20 12:03:11 +02:00
public function handleID ( $request ) {
$models = $this -> parentController -> getManagedModels ();
$model = $this -> getModelClass ();
if ( isset ( $models [ $model ][ 'record_controller' ])) {
$class = $models [ $model ][ 'record_controller' ];
} else {
$class = $this -> parentController -> stat ( 'record_controller_class' );
}
2008-08-09 09:03:24 +02:00
return new $class ( $this , $request );
2008-08-09 05:54:55 +02:00
}
2009-05-16 07:58:54 +02:00
// -----------------------------------------------------------------------------------------------------------------
/**
* Get a combination of the Search , Import and Create forms that can be inserted into a { @ link ModelAdmin } sidebar .
*
* @ return string
*/
public function getModelSidebar () {
return $this -> renderWith ( 'ModelSidebar' );
}
2008-08-06 05:28:25 +02:00
/**
* Get a search form for a single { @ link DataObject } subclass .
*
2008-08-09 05:54:55 +02:00
* @ return Form
2008-08-06 05:28:25 +02:00
*/
2008-08-09 05:54:55 +02:00
public function SearchForm () {
$context = singleton ( $this -> modelClass ) -> getDefaultSearchContext ();
2008-08-11 02:03:57 +02:00
$fields = $context -> getSearchFields ();
$columnSelectionField = $this -> ColumnSelectionField ();
$fields -> push ( $columnSelectionField );
2009-03-12 17:42:31 +01:00
$validator = new RequiredFields ();
$validator -> setJavascriptValidationHandler ( 'none' );
2008-08-06 05:28:25 +02:00
2008-08-09 05:54:55 +02:00
$form = new Form ( $this , " SearchForm " ,
2008-08-11 02:03:57 +02:00
$fields ,
2008-08-06 05:28:25 +02:00
new FieldSet (
2009-04-29 03:44:28 +02:00
new FormAction ( 'search' , _t ( 'MemberTableField.SEARCH' , 'Search' )),
2008-08-11 02:21:44 +02:00
$clearAction = new ResetFormAction ( 'clearsearch' , _t ( 'ModelAdmin.CLEAR_SEARCH' , 'Clear Search' ))
2009-03-12 17:42:31 +01:00
),
$validator
2008-08-06 05:28:25 +02:00
);
2008-08-11 05:03:52 +02:00
//$form->setFormAction(Controller::join_links($this->Link(), "search"));
2008-08-09 04:16:46 +02:00
$form -> setFormMethod ( 'get' );
2008-08-09 05:54:55 +02:00
$form -> setHTMLID ( " Form_SearchForm_ " . $this -> modelClass );
2008-08-11 02:21:44 +02:00
$clearAction -> useButtonTag = true ;
$clearAction -> addExtraClass ( 'minorAction' );
2008-11-04 02:32:45 +01:00
2008-08-06 05:28:25 +02:00
return $form ;
}
2008-08-11 02:03:57 +02:00
2008-11-05 09:55:07 +01:00
/**
* Create a form that consists of one button
* that directs to a give model ' s Add form
*/
public function CreateForm () {
$modelName = $this -> modelClass ;
2009-02-03 21:34:13 +01:00
if ( $this -> hasMethod ( 'alternatePermissionCheck' )) {
if ( ! $this -> alternatePermissionCheck ()) return false ;
} else {
if ( ! singleton ( $modelName ) -> canCreate ( Member :: currentUser ())) return false ;
2008-11-05 09:55:07 +01:00
}
2008-11-06 00:23:23 +01:00
$buttonLabel = sprintf ( _t ( 'ModelAdmin.CREATEBUTTON' , " Create '%s' " , PR_MEDIUM , " Create a new instance from a model class " ), singleton ( $modelName ) -> i18n_singular_name ());
2008-11-05 09:55:07 +01:00
$actions = new FieldSet (
2008-11-06 00:23:23 +01:00
$createButton = new FormAction ( 'add' , $buttonLabel )
2008-11-05 09:55:07 +01:00
);
2009-02-03 21:34:13 +01:00
2008-11-06 00:23:23 +01:00
$createButton -> dontEscape = true ;
2008-11-05 09:55:07 +01:00
2009-03-12 17:42:31 +01:00
$validator = new RequiredFields ();
$validator -> setJavascriptValidationHandler ( 'none' );
return new Form ( $this , " CreateForm " , new FieldSet (), $actions , $validator );
2008-11-05 09:55:07 +01:00
}
/**
* Generate a CSV import form for a single { @ link DataObject } subclass .
*
* @ return Form
*/
public function ImportForm () {
$modelName = $this -> modelClass ;
$importers = $this -> parentController -> getModelImporters ();
2008-12-04 23:38:58 +01:00
if ( ! $importers || ! isset ( $importers [ $modelName ])) return false ;
2008-11-05 09:55:07 +01:00
2009-08-17 07:26:34 +02:00
if ( ! singleton ( $modelName ) -> canCreate ( Member :: currentUser ())) return false ;
2008-11-05 09:55:07 +01:00
$fields = new FieldSet (
new HiddenField ( 'ClassName' , _t ( 'ModelAdmin.CLASSTYPE' ), $modelName ),
new FileField ( '_CsvFile' , false )
);
// get HTML specification for each import (column names etc.)
$importerClass = $importers [ $modelName ];
$importer = new $importerClass ( $modelName );
$spec = $importer -> getImportSpec ();
$specFields = new DataObjectSet ();
foreach ( $spec [ 'fields' ] as $name => $desc ) {
$specFields -> push ( new ArrayData ( array ( 'Name' => $name , 'Description' => $desc )));
}
$specRelations = new DataObjectSet ();
foreach ( $spec [ 'relations' ] as $name => $desc ) {
$specRelations -> push ( new ArrayData ( array ( 'Name' => $name , 'Description' => $desc )));
}
$specHTML = $this -> customise ( array (
2008-12-04 23:38:58 +01:00
'ModelName' => Convert :: raw2att ( $modelName ),
2008-11-05 09:55:07 +01:00
'Fields' => $specFields ,
'Relations' => $specRelations ,
)) -> renderWith ( 'ModelAdmin_ImportSpec' );
$fields -> push ( new LiteralField ( " SpecFor { $modelName } " , $specHTML ));
$actions = new FieldSet (
new FormAction ( 'import' , _t ( 'ModelAdmin.IMPORT' , 'Import from CSV' ))
);
2009-03-12 17:42:31 +01:00
$validator = new RequiredFields ();
$validator -> setJavascriptValidationHandler ( 'none' );
2008-11-05 09:55:07 +01:00
$form = new Form (
$this ,
" ImportForm " ,
$fields ,
2009-03-12 17:42:31 +01:00
$actions ,
$validator
2008-11-05 09:55:07 +01:00
);
return $form ;
}
/**
* Imports the submitted CSV file based on specifications given in
* { @ link self :: model_importers } .
* Redirects back with a success / failure message .
*
* @ todo Figure out ajax submission of files via jQuery . form plugin
*
2008-12-04 23:38:58 +01:00
* @ param array $data
* @ param Form $form
* @ param HTTPRequest $request
2008-11-05 09:55:07 +01:00
*/
function import ( $data , $form , $request ) {
2008-12-04 23:38:58 +01:00
$modelName = $data [ 'ClassName' ];
2008-11-05 09:55:07 +01:00
$importers = $this -> parentController -> getModelImporters ();
$importerClass = $importers [ $modelName ];
$loader = new $importerClass ( $data [ 'ClassName' ]);
2008-12-04 23:38:58 +01:00
// File wasn't properly uploaded, show a reminder to the user
if ( empty ( $_FILES [ '_CsvFile' ][ 'tmp_name' ])) {
$form -> sessionMessage ( _t ( 'ModelAdmin.NOCSVFILE' , 'Please browse for a CSV file to import' ), 'good' );
Director :: redirectBack ();
return false ;
}
2008-11-05 09:55:07 +01:00
$results = $loader -> load ( $_FILES [ '_CsvFile' ][ 'tmp_name' ]);
$message = '' ;
if ( $results -> CreatedCount ()) $message .= sprintf (
2009-01-05 07:17:59 +01:00
_t ( 'ModelAdmin.IMPORTEDRECORDS' , " Imported %s records. " ),
2008-11-05 09:55:07 +01:00
$results -> CreatedCount ()
);
if ( $results -> UpdatedCount ()) $message .= sprintf (
_t ( 'ModelAdmin.UPDATEDRECORDS' , " Updated %s records. " ),
$results -> UpdatedCount ()
);
if ( $results -> DeletedCount ()) $message .= sprintf (
_t ( 'ModelAdmin.DELETEDRECORDS' , " Deleted %s records. " ),
$results -> DeletedCount ()
);
if ( ! $results -> CreatedCount () && ! $results -> UpdatedCount ()) $message .= _t ( 'ModelAdmin.NOIMPORT' , " Nothing to import " );
2008-12-04 23:38:58 +01:00
$form -> sessionMessage ( $message , 'good' );
Director :: redirectBack ();
2008-11-05 09:55:07 +01:00
}
2009-01-05 07:17:59 +01:00
/**
* Return the columns available in the column selection field .
* Overload this to make other columns available
*/
public function columnsAvailable () {
return singleton ( $this -> modelClass ) -> summaryFields ();
}
/**
* Return the columns selected by default in the column selection field .
* Overload this to make other columns selected by default
*/
public function columnsSelectedByDefault () {
return array_keys ( singleton ( $this -> modelClass ) -> summaryFields ());
}
2008-08-11 02:03:57 +02:00
/**
* Give the flexibilility to show variouse combination of columns in the search result table
*/
2009-01-05 07:17:59 +01:00
public function ColumnSelectionField () {
2008-08-11 02:03:57 +02:00
$model = singleton ( $this -> modelClass );
2009-01-05 07:17:59 +01:00
$source = $this -> columnsAvailable ();
2008-12-04 23:38:58 +01:00
// select all fields by default
2009-01-05 07:17:59 +01:00
$value = $this -> columnsSelectedByDefault ();
2008-08-29 00:57:18 +02:00
// Reorder the source so that you read items down the column and then across
$columnisedSource = array ();
$keys = array_keys ( $source );
$midPoint = ceil ( sizeof ( $source ) / 2 );
for ( $i = 0 ; $i < $midPoint ; $i ++ ) {
$key1 = $keys [ $i ];
2008-10-10 14:15:31 +02:00
$columnisedSource [ $key1 ] = $model -> fieldLabel ( $source [ $key1 ]);
2008-08-29 00:57:18 +02:00
// If there are an odd number of items, the last item will be unset
if ( isset ( $keys [ $i + $midPoint ])) {
$key2 = $keys [ $i + $midPoint ];
2008-10-10 14:15:31 +02:00
$columnisedSource [ $key2 ] = $model -> fieldLabel ( $source [ $key2 ]);
2008-08-29 00:57:18 +02:00
}
}
2008-10-10 14:15:31 +02:00
2008-08-29 00:57:18 +02:00
$checkboxes = new CheckboxSetField ( " ResultAssembly " , false , $columnisedSource , $value );
2008-08-11 02:03:57 +02:00
$field = new CompositeField (
2008-08-11 02:21:44 +02:00
new LiteralField (
" ToggleResultAssemblyLink " ,
sprintf ( " <a class= \" form_frontend_function toggle_result_assembly \" href= \" # \" >%s</a> " ,
_t ( 'ModelAdmin.CHOOSE_COLUMNS' , 'Select result columns...' )
)
),
2008-08-11 02:03:57 +02:00
$checkboxesBlock = new CompositeField (
$checkboxes ,
new LiteralField ( " ClearDiv " , " <div class= \" clear \" ></div> " ),
2008-10-10 14:15:31 +02:00
new LiteralField (
" TickAllAssemblyLink " ,
sprintf (
" <a class= \" form_frontend_function tick_all_result_assembly \" href= \" # \" >%s</a> " ,
_t ( 'ModelAdmin.SELECTALL' , 'select all' )
)
),
new LiteralField (
" UntickAllAssemblyLink " ,
sprintf (
" <a class= \" form_frontend_function untick_all_result_assembly \" href= \" # \" >%s</a> " ,
_t ( 'ModelAdmin.SELECTNONE' , 'select none' )
)
)
2008-08-11 02:03:57 +02:00
)
);
2008-10-16 10:45:14 +02:00
$field -> addExtraClass ( " ResultAssemblyBlock " );
$checkboxesBlock -> addExtraClass ( " hidden " );
2008-08-11 02:03:57 +02:00
return $field ;
}
2008-08-06 05:43:48 +02:00
/**
2008-08-09 04:00:40 +02:00
* Action to render a data object collection , using the model context to provide filters
* and paging .
2008-08-09 05:54:55 +02:00
*
2008-08-11 01:29:30 +02:00
* @ return string
2008-08-06 05:43:48 +02:00
*/
2008-08-11 05:03:52 +02:00
function search ( $request , $form ) {
2008-09-13 06:45:30 +02:00
// Get the results form to be rendered
2008-10-07 05:55:43 +02:00
$resultsForm = $this -> ResultsForm ( array_merge ( $form -> getData (), $request ));
2008-09-13 06:45:30 +02:00
// Before rendering, let's get the total number of results returned
$tableField = $resultsForm -> Fields () -> fieldByName ( $this -> modelClass );
$numResults = $tableField -> TotalCount ();
if ( $numResults ) {
2008-10-10 14:15:31 +02:00
return new HTTPResponse (
$resultsForm -> forTemplate (),
200 ,
sprintf (
_t ( 'ModelAdmin.FOUNDRESULTS' , " Your search found %s matching items " ),
$numResults
)
);
2008-09-13 06:45:30 +02:00
} else {
2008-10-10 14:15:31 +02:00
return new HTTPResponse (
$resultsForm -> forTemplate (),
2008-11-12 03:18:11 +01:00
200 ,
2008-10-10 14:15:31 +02:00
_t ( 'ModelAdmin.NORESULTS' , " Your search didn't return any matching items " )
);
2008-09-13 06:45:30 +02:00
}
2008-08-11 01:29:30 +02:00
}
/**
* Gets the search query generated on the SearchContext from
* { @ link DataObject :: getDefaultSearchContext ()},
* and the current GET parameters on the request .
*
* @ return SQLQuery
*/
2008-08-11 05:03:52 +02:00
function getSearchQuery ( $searchCriteria ) {
2008-08-11 01:29:30 +02:00
$context = singleton ( $this -> modelClass ) -> getDefaultSearchContext ();
2008-08-11 05:03:52 +02:00
return $context -> getQuery ( $searchCriteria );
2008-08-11 01:29:30 +02:00
}
2008-12-04 23:38:58 +01:00
/**
* Returns all columns used for tabular search results display .
* Defaults to all fields specified in { @ link DataObject -> summaryFields ()} .
*
* @ param array $searchCriteria Limit fields by populating the 'ResultsAssembly' key
* @ param boolean $selectedOnly Limit by ' ResultsAssempty
*/
function getResultColumns ( $searchCriteria , $selectedOnly = true ) {
2008-08-09 05:54:55 +02:00
$model = singleton ( $this -> modelClass );
2008-08-19 12:08:52 +02:00
2009-01-05 07:17:59 +01:00
$summaryFields = $this -> columnsAvailable ();
2008-12-04 23:38:58 +01:00
2009-06-04 23:12:16 +02:00
$resultAssembly = $searchCriteria [ 'ResultAssembly' ];
if ( $selectedOnly && $resultAssembly ) {
2008-12-04 23:38:58 +01:00
if ( ! is_array ( $resultAssembly )) {
$explodedAssembly = split ( ' *, *' , $resultAssembly );
$resultAssembly = array ();
foreach ( $explodedAssembly as $item ) $resultAssembly [ $item ] = true ;
}
return array_intersect_key ( $summaryFields , $resultAssembly );
} else {
return $summaryFields ;
2008-08-11 02:03:57 +02:00
}
2008-08-19 12:08:52 +02:00
}
/**
2009-05-19 05:25:47 +02:00
* Creates and returns the result table field for resultsForm .
* Uses { @ link resultsTableClassName ()} to initialise the formfield .
* Method is called from { @ link ResultsForm } .
2008-08-19 12:08:52 +02:00
*
2009-05-19 05:25:47 +02:00
* @ param array $searchCriteria passed through from ResultsForm
*
* @ return TableListField
2008-08-19 12:08:52 +02:00
*/
2009-05-19 05:25:47 +02:00
function getResultsTable ( $searchCriteria ) {
2008-08-19 12:08:52 +02:00
$summaryFields = $this -> getResultColumns ( $searchCriteria );
2008-12-04 23:38:58 +01:00
$className = $this -> parentController -> resultsTableClassName ();
$tf = new $className (
2008-08-09 08:29:50 +02:00
$this -> modelClass ,
$this -> modelClass ,
$summaryFields
);
2009-05-19 05:25:47 +02:00
2008-08-11 05:03:52 +02:00
$tf -> setCustomQuery ( $this -> getSearchQuery ( $searchCriteria ));
2008-08-11 01:29:30 +02:00
$tf -> setPageSize ( $this -> parentController -> stat ( 'page_length' ));
2008-08-11 01:17:51 +02:00
$tf -> setShowPagination ( true );
2008-08-14 06:43:15 +02:00
// @todo Remove records that can't be viewed by the current user
$tf -> setPermissions ( array_merge ( array ( 'view' , 'export' ), TableListField :: permissions_for_object ( $this -> modelClass )));
2009-05-19 05:25:47 +02:00
2008-12-04 23:38:58 +01:00
// csv export settings (select all columns regardless of user checkbox settings in 'ResultsAssembly')
$exportFields = $this -> getResultColumns ( $searchCriteria , false );
$tf -> setFieldListCsv ( $exportFields );
2009-05-19 05:25:47 +02:00
2008-08-11 01:17:51 +02:00
$url = '<a href=\"' . $this -> Link () . '/$ID/edit\">$value</a>' ;
2008-08-09 08:29:50 +02:00
$tf -> setFieldFormatting ( array_combine ( array_keys ( $summaryFields ), array_fill ( 0 , count ( $summaryFields ), $url )));
2009-05-19 05:25:47 +02:00
return $tf ;
}
/**
* Shows results from the " search " action in a TableListField .
*
* @ uses getResultsTable ()
*
* @ return Form
*/
function ResultsForm ( $searchCriteria ) {
if ( $searchCriteria instanceof HTTPRequest ) $searchCriteria = $searchCriteria -> getVars ();
$tf = $this -> getResultsTable ( $searchCriteria );
2008-08-09 08:29:50 +02:00
// implemented as a form to enable further actions on the resultset
// (serverside sorting, export as CSV, etc)
$form = new Form (
$this ,
'ResultsForm' ,
new FieldSet (
2008-10-16 15:27:12 +02:00
new HeaderField ( 'SearchResultsHeader' , _t ( 'ModelAdmin.SEARCHRESULTS' , 'Search Results' ), 2 ),
2008-08-09 08:29:50 +02:00
$tf
),
2008-11-18 00:03:39 +01:00
new FieldSet (
2009-01-05 07:17:59 +01:00
new FormAction ( " goBack " , _t ( 'ModelAdmin.GOBACK' , " Back " )),
2008-11-18 00:03:39 +01:00
new FormAction ( " goForward " , _t ( 'ModelAdmin.GOFORWARD' , " Forward " ))
)
2008-08-09 08:29:50 +02:00
);
2008-08-11 01:29:30 +02:00
2008-08-26 03:54:53 +02:00
// Include the search criteria on the results form URL, but not dodgy variables like those below
$filteredCriteria = $searchCriteria ;
unset ( $filteredCriteria [ 'ctf' ]);
unset ( $filteredCriteria [ 'url' ]);
unset ( $filteredCriteria [ 'action_search' ]);
2008-11-18 00:03:39 +01:00
if ( isset ( $filteredCriteria [ 'Investors__PEFirm__IsPECMember' ]) && ! $filteredCriteria [ 'Investors__PEFirm__IsPECMember' ]) unset ( $filteredCriteria [ 'Investors__PEFirm__IsPECMember' ]);
2008-12-04 23:38:58 +01:00
2008-08-26 03:54:53 +02:00
$form -> setFormAction ( $this -> Link () . '/ResultsForm?' . http_build_query ( $filteredCriteria ));
2008-08-11 01:29:30 +02:00
return $form ;
2008-08-09 05:54:55 +02:00
}
2008-08-11 01:29:30 +02:00
2008-08-09 05:54:55 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Create a new model record .
*
* @ param unknown_type $request
* @ return unknown
*/
function add ( $request ) {
2008-10-10 14:15:31 +02:00
return new HTTPResponse (
$this -> AddForm () -> forAjaxTemplate (),
200 ,
sprintf (
_t ( 'ModelAdmin.ADDFORM' , " Fill out this form to add a %s to the database. " ),
$this -> modelClass
)
);
2008-08-09 05:54:55 +02:00
}
/**
2009-03-18 15:13:03 +01:00
* Returns a form suitable for adding a new model , falling back on the default edit form .
*
* Caution : The add - form shows a DataObject ' s { @ link DataObject -> getCMSFields ()} method on a record
* that doesn ' t exist in the database yet , hence has no ID . This means the { @ link DataObject -> getCMSFields ()}
* implementation has to ensure that no fields are added which would rely on a
* record ID being present , e . g . { @ link HasManyComplexTableField } .
*
* Example :
* < code >
* function getCMSFields () {
* $fields = parent :: getCMSFields ();
* if ( $this -> exists ()) {
* $ctf = new HasManyComplexTableField ( $this , 'MyRelations' , 'MyRelation' );
* $fields -> addFieldToTab ( 'Root.Main' , $ctf );
* }
* return $fields ;
* }
* </ code >
2009-02-03 04:46:15 +01:00
*
* @ return Form
2008-08-09 05:54:55 +02:00
*/
public function AddForm () {
$newRecord = new $this -> modelClass ();
2009-02-03 04:46:15 +01:00
2009-02-03 04:22:20 +01:00
if ( $newRecord -> canCreate ()){
if ( $newRecord -> hasMethod ( 'getCMSAddFormFields' )) {
$fields = $newRecord -> getCMSAddFormFields ();
} else {
$fields = $newRecord -> getCMSFields ();
}
2009-02-03 04:46:15 +01:00
2009-02-03 04:22:20 +01:00
$validator = ( $newRecord -> hasMethod ( 'getCMSValidator' )) ? $newRecord -> getCMSValidator () : null ;
2009-03-12 17:42:31 +01:00
if ( ! $validator ) $validator = new RequiredFields ();
$validator -> setJavascriptValidationHandler ( 'none' );
2009-02-03 04:46:15 +01:00
$actions = new FieldSet (
2009-02-03 04:22:20 +01:00
new FormAction ( " doCreate " , _t ( 'ModelAdmin.ADDBUTTON' , " Add " ))
);
2009-02-03 04:46:15 +01:00
2009-02-03 04:22:20 +01:00
$form = new Form ( $this , " AddForm " , $fields , $actions , $validator );
2009-02-03 04:46:15 +01:00
$form -> loadDataFrom ( $newRecord );
2009-02-03 04:22:20 +01:00
return $form ;
}
2009-02-03 04:46:15 +01:00
}
2008-08-09 05:54:55 +02:00
function doCreate ( $data , $form , $request ) {
$className = $this -> getModelClass ();
$model = new $className ();
2008-08-26 02:25:22 +02:00
// We write before saveInto, since this will let us save has-many and many-many relationships :-)
$model -> write ();
2008-08-09 05:54:55 +02:00
$form -> saveInto ( $model );
$model -> write ();
2009-03-12 17:42:31 +01:00
if ( Director :: is_ajax ()) {
$recordController = new ModelAdmin_RecordController ( $this , $request , $model -> ID );
return new HTTPResponse (
$recordController -> EditForm () -> forAjaxTemplate (),
200 ,
sprintf (
_t ( 'ModelAdmin.LOADEDFOREDITING' , " Loaded '%s' for editing. " ),
$model -> Title
)
);
} else {
Director :: redirect ( Controller :: join_links ( $this -> Link (), $model -> ID , 'edit' ));
}
2008-08-09 05:54:55 +02:00
}
}
/**
* Handles operations on a single record from a managed model .
*
2009-03-22 23:58:18 +01:00
* @ package cms
* @ subpackage core
2008-08-09 05:54:55 +02:00
* @ todo change the parent controller varname to indicate the model scaffolding functionality in ModelAdmin
*/
class ModelAdmin_RecordController extends Controller {
protected $parentController ;
protected $currentRecord ;
static $allowed_actions = array ( 'edit' , 'view' , 'EditForm' , 'ViewForm' );
2009-03-12 17:42:31 +01:00
function __construct ( $parentController , $request , $recordID = null ) {
2008-08-09 05:54:55 +02:00
$this -> parentController = $parentController ;
$modelName = $parentController -> getModelClass ();
2009-03-12 17:42:31 +01:00
$recordID = ( $recordID ) ? $recordID : $request -> param ( 'Action' );
2008-08-09 05:54:55 +02:00
$this -> currentRecord = DataObject :: get_by_id ( $modelName , $recordID );
2008-11-04 02:32:45 +01:00
parent :: __construct ();
2008-08-09 05:54:55 +02:00
}
/**
* Link fragment - appends the current record ID to the URL .
*
*/
function Link () {
return Controller :: join_links ( $this -> parentController -> Link (), " / { $this -> currentRecord -> ID } " );
2008-08-06 05:43:48 +02:00
}
2008-08-09 05:54:55 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////
2008-08-06 05:43:48 +02:00
2008-08-09 04:00:40 +02:00
/**
2008-08-09 05:54:55 +02:00
* Edit action - shows a form for editing this record
*/
function edit ( $request ) {
if ( $this -> currentRecord ) {
2008-08-11 02:21:44 +02:00
if ( Director :: is_ajax ()) {
2008-10-10 14:15:31 +02:00
return new HTTPResponse (
$this -> EditForm () -> forAjaxTemplate (),
200 ,
sprintf (
_t ( 'ModelAdmin.LOADEDFOREDITING' , " Loaded '%s' for editing. " ),
$this -> currentRecord -> Title
)
);
2008-08-11 02:21:44 +02:00
} else {
2008-08-26 02:25:22 +02:00
// This is really quite ugly; to fix will require a change in the way that customise() works. :-(
2008-09-12 03:50:08 +02:00
return $this -> parentController -> parentController -> customise ( array (
2008-08-26 02:25:22 +02:00
'Right' => $this -> parentController -> parentController -> customise ( array (
'EditForm' => $this -> EditForm ()
)) -> renderWith ( 'ModelAdmin_right' )
)) -> renderWith ( array ( 'ModelAdmin' , 'LeftAndMain' ));
return ;
2008-08-11 02:21:44 +02:00
}
2008-08-09 05:54:55 +02:00
} else {
2008-10-10 14:15:31 +02:00
return _t ( 'ModelAdmin.ITEMNOTFOUND' , " I can't find that item " );
2008-08-09 05:54:55 +02:00
}
}
/**
* Returns a form for editing the attached model
2008-08-09 04:00:40 +02:00
*/
2008-08-09 05:54:55 +02:00
public function EditForm () {
$fields = $this -> currentRecord -> getCMSFields ();
$fields -> push ( new HiddenField ( " ID " ));
2009-03-12 17:42:31 +01:00
$validator = ( $this -> currentRecord -> hasMethod ( 'getCMSValidator' )) ? $this -> currentRecord -> getCMSValidator () : new RequiredFields ();
$validator -> setJavascriptValidationHandler ( 'none' );
2008-08-09 05:54:55 +02:00
2008-12-04 23:38:58 +01:00
$actions = $this -> currentRecord -> getCMSActions ();
2009-02-03 04:22:20 +01:00
if ( $this -> currentRecord -> canEdit ( Member :: currentUser ())){
$actions -> push ( new FormAction ( " doSave " , _t ( 'ModelAdmin.SAVE' , " Save " )));
} else {
$fields = $fields -> makeReadonly ();
}
2008-08-09 05:54:55 +02:00
2008-08-11 02:21:44 +02:00
if ( $this -> currentRecord -> canDelete ( Member :: currentUser ())) {
2008-10-10 14:15:31 +02:00
$actions -> insertFirst ( $deleteAction = new FormAction ( 'doDelete' , _t ( 'ModelAdmin.DELETE' , 'Delete' )));
2008-08-11 02:21:44 +02:00
$deleteAction -> addExtraClass ( 'delete' );
}
2008-08-19 07:18:11 +02:00
2008-10-10 14:15:31 +02:00
$actions -> insertFirst ( new FormAction ( " goBack " , _t ( 'ModelAdmin.GOBACK' , " Back " )));
2008-08-11 02:21:44 +02:00
2008-08-09 05:54:55 +02:00
$form = new Form ( $this , " EditForm " , $fields , $actions , $validator );
$form -> loadDataFrom ( $this -> currentRecord );
return $form ;
}
2008-08-09 06:53:34 +02:00
/**
* Postback action to save a record
*
* @ param array $data
* @ param Form $form
* @ param HTTPRequest $request
* @ return mixed
*/
2008-08-09 05:54:55 +02:00
function doSave ( $data , $form , $request ) {
2008-08-11 02:14:48 +02:00
$form -> saveInto ( $this -> currentRecord );
2009-05-26 08:01:36 +02:00
try {
$this -> currentRecord -> write ();
} catch ( ValidationException $e ) {
$form -> sessionMessage ( $e -> getResult () -> message (), 'bad' );
}
2008-08-06 05:43:48 +02:00
2008-08-09 05:54:55 +02:00
// Behaviour switched on ajax.
if ( Director :: is_ajax ()) {
return $this -> edit ( $request );
} else {
Director :: redirectBack ();
}
}
2008-08-11 02:21:44 +02:00
/**
* Delete the current record
*/
public function doDelete ( $data , $form , $request ) {
if ( $this -> currentRecord -> canDelete ( Member :: currentUser ())) {
$this -> currentRecord -> delete ();
Director :: redirect ( $this -> parentController -> Link ());
}
else Director :: redirectBack ();
return ;
}
2008-08-09 05:54:55 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
2008-08-09 06:53:34 +02:00
* Renders the record view template .
*
* @ param HTTPRequest $request
* @ return mixed
2008-08-09 05:54:55 +02:00
*/
function view ( $request ) {
2009-02-03 21:34:13 +01:00
if ( $this -> currentRecord ) {
2008-08-09 05:54:55 +02:00
$form = $this -> ViewForm ();
2008-08-09 06:38:44 +02:00
return $form -> forAjaxTemplate ();
2008-08-09 05:54:55 +02:00
} else {
2008-10-10 14:15:31 +02:00
return _t ( 'ModelAdmin.ITEMNOTFOUND' );
2008-08-06 05:43:48 +02:00
}
}
2008-08-09 05:54:55 +02:00
/**
* Returns a form for viewing the attached model
2008-08-09 06:53:34 +02:00
*
* @ return Form
2008-08-09 05:54:55 +02:00
*/
public function ViewForm () {
$fields = $this -> currentRecord -> getCMSFields ();
$form = new Form ( $this , " EditForm " , $fields , new FieldSet ());
$form -> loadDataFrom ( $this -> currentRecord );
$form -> makeReadonly ();
return $form ;
}
2008-08-06 05:43:48 +02:00
2008-08-09 05:54:55 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////
2008-08-06 05:43:48 +02:00
2008-08-09 05:54:55 +02:00
function index () {
Director :: redirect ( Controller :: join_links ( $this -> Link (), 'edit' ));
}
2009-03-24 07:17:21 +01:00
function getCurrentRecord (){
return $this -> currentRecord ;
}
2008-08-06 05:28:25 +02:00
}
2008-08-09 05:54:55 +02:00
2008-08-06 05:28:25 +02:00
?>