Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • _DiffEngine
  • _DiffOp
  • _DiffOp_Add
  • _DiffOp_Change
  • _DiffOp_Copy
  • _DiffOp_Delete
  • BookingOrderAdmin
  • BookingOrderAdmin_CollectionController
  • CatalogAdmin_CollectionController
  • CatalogAdmin_RecordController
  • CMSActionOptionsForm
  • Diff
  • GuestbookAdmin_CollectionController
  • ImportCatalog1C_ProductProp_Admin
  • LeftAndMain
  • LeftAndMainDecorator
  • LoggerAdmin_CollectionController
  • LoggerAdmin_RecordController
  • MappedDiff
  • ModelAdmin
  • ModelAdmin_CollectionController
  • ModelAdmin_RecordController
  • MonumentAdmin
  • OrderAdmin_CollectionController
  • OrderAdmin_RecordController
  • PaymentAdmin
  • PaymentAdmin_CollectionController
  • ProductImport1CAdmin
  • ProductImport1CAdmin_CollectionController
  • ProductImportAdmin
  • ProductImportAdmin_CollectionController
  • RealtyImportAdmin
  • RealtyImportAdmin_CollectionController
  • RedirectEntry_Admin
  • RoomServiceAdmin
  • ShippingMethodAdmin_CollectionController
  • SubsiteAdmin_CollectionController
  • VAT_Admin
  • VKNotificationQueueAdmin
   1 <?php
   2 /**
   3  * Generates a three-pane UI for editing model classes,
   4  * with an automatically generated search panel, tabular results
   5  * and edit forms.
   6  * Relies on data such as {@link DataObject::$db} and {@DataObject::getCMSFields()}
   7  * to scaffold interfaces "out of the box", while at the same time providing
   8  * flexibility to customize the default output.
   9  * 
  10  * Add a route (note - this doc is not currently in sync with the code, need to update)
  11  * <code>
  12  * Director::addRules(50, array('admin/mymodel/$Class/$Action/$ID' => 'MyModelAdmin'));
  13  * </code>
  14  *
  15  * @todo saving logic (should mostly use Form->saveInto() and iterate over relations)
  16  * @todo ajax form loading and saving
  17  * @todo ajax result display
  18  * @todo relation formfield scaffolding (one tab per relation) - relations don't have DBField sublclasses, we do
  19  *  we define the scaffold defaults. can be ComplexTableField instances for a start. 
  20  * @todo has_many/many_many relation autocomplete field (HasManyComplexTableField doesn't work well with larger datasets)
  21  * 
  22  * Long term TODOs:
  23  * @todo Hook into RESTful interface on DataObjects (yet to be developed)
  24  * @todo Permission control via datamodel and Form class
  25  * 
  26  * @uses SearchContext
  27  * 
  28  * @package cms
  29  * @subpackage core
  30  */
  31 abstract class ModelAdmin extends LeftAndMain {
  32 
  33     static $url_rule = '/$Action';  
  34     
  35     /**
  36      * List of all managed {@link DataObject}s in this interface.
  37      *
  38      * Simple notation with class names only:
  39      * <code>
  40      * array('MyObjectClass','MyOtherObjectClass')
  41      * </code>
  42      * 
  43      * Extended notation with options (e.g. custom titles):
  44      * <code>
  45      * array(
  46      *   'MyObjectClass' => array('title' => "Custom title")
  47      * )
  48      * </code>
  49      * 
  50      * Available options:
  51      * - 'title': Set custom titles for the tabs or dropdown names
  52      * - 'collection_controller': Set a custom class to use as a collection controller for this model
  53      * - 'record_controller': Set a custom class to use as a record controller for this model
  54      *
  55      * @var array|string
  56      */
  57     public static $managed_models = null;
  58     
  59     /**
  60      * More actions are dynamically added in {@link defineMethods()} below.
  61      */
  62     public static $allowed_actions = array(
  63         'add',
  64         'edit',
  65         'delete',
  66         'import',
  67         'renderimportform',
  68         'handleList',
  69         'handleItem',
  70         'ImportForm'
  71     );
  72     
  73     /**
  74      * @param string $collection_controller_class Override for controller class
  75      */
  76     public static $collection_controller_class = "ModelAdmin_CollectionController";
  77     
  78     /**
  79      * @param string $collection_controller_class Override for controller class
  80      */
  81     public static $record_controller_class = "ModelAdmin_RecordController";
  82     
  83     /**
  84      * Forward control to the default action handler
  85      */
  86     public static $url_handlers = array(
  87         '$Action' => 'handleAction'
  88     );
  89     
  90     /**
  91      * Model object currently in manipulation queue. Used for updating Link to point
  92      * to the correct generic data object in generated URLs.
  93      *
  94      * @var string
  95      */
  96     private $currentModel = false;
  97         
  98     /**
  99      * List of all {@link DataObject}s which can be imported through
 100      * a subclass of {@link BulkLoader} (mostly CSV data).
 101      * By default {@link CsvBulkLoader} is used, assuming a standard mapping
 102      * of column names to {@link DataObject} properties/relations.
 103      * 
 104      * e.g. "BlogEntry" => "BlogEntryCsvBulkLoader"
 105      *
 106      * @var array
 107      */
 108     public static $model_importers = null;
 109     
 110     /**
 111      * Amount of results showing on a single page.
 112      *
 113      * @var int
 114      */
 115     public static $page_length = 30;
 116     
 117     /**
 118      * Class name of the form field used for the results list.  Overloading this in subclasses
 119      * can let you customise the results table field.
 120      */
 121     protected $resultsTableClassName = 'TableListField';
 122 
 123     /**
 124      * Return {@link $this->resultsTableClassName}
 125      */
 126     public function resultsTableClassName() {
 127         return $this->resultsTableClassName;
 128     }
 129     
 130     /**
 131      * Initialize the model admin interface. Sets up embedded jquery libraries and requisite plugins.
 132      * 
 133      * @todo remove reliance on urlParams
 134      */
 135     public function init() {
 136         parent::init();
 137         
 138         if($this->getRequest()->requestVar("Locale")) {
 139             $this->Locale = $this->getRequest()->requestVar("Locale");
 140         } elseif($this->getRequest()->requestVar("locale")) {
 141             $this->Locale = $this->getRequest()->requestVar("locale");
 142         } else {
 143             $this->Locale = Translatable::default_locale();
 144         }
 145         Translatable::set_current_locale($this->Locale);
 146         
 147         // security check for valid models
 148         if(isset($this->urlParams['Action']) && !in_array($this->urlParams['Action'], $this->getManagedModels())) {
 149             //user_error('ModelAdmin::init(): Invalid Model class', E_USER_ERROR);
 150         }
 151         
 152         Requirements::css(CMS_DIR . '/css/ModelAdmin.css'); // standard layout formatting for management UI
 153         Requirements::css(CMS_DIR . '/css/silverstripe.tabs.css'); // follows the jQuery UI theme conventions
 154         
 155         Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
 156         Requirements::javascript(THIRDPARTY_DIR . '/jquery-form/jquery.form.js');
 157         Requirements::javascript(THIRDPARTY_DIR . '/jquery-effen/jquery.fn.js');
 158         Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
 159         Requirements::javascript(CMS_DIR . '/javascript/ModelAdmin.js');
 160     }
 161     
 162     /**
 163      * overwrite the static page_length of the admin panel, 
 164      * should be called in the project _config file.
 165      */
 166     static function set_page_length($length){
 167         self::$page_length = $length;
 168     }
 169     
 170     /**
 171      * Return the static page_length of the admin, default as 30
 172      */
 173     static function get_page_length(){
 174         return self::$page_length;
 175     } 
 176     
 177     /**
 178      * Return the class name of the collection controller
 179      *
 180      * @param string $model model name to get the controller for
 181      * @return string the collection controller class
 182      */
 183     function getCollectionControllerClass($model) {
 184         $models = $this->getManagedModels();
 185         
 186         if(isset($models[$model]['collection_controller'])) {
 187             $class = $models[$model]['collection_controller'];
 188         } else {
 189             $class = $this->stat('collection_controller_class');
 190         }
 191         
 192         return $class;
 193     }
 194     
 195     /**
 196      * Return the class name of the record controller
 197      *
 198      * @param string $model model name to get the controller for
 199      * @return string the record controller class
 200      */
 201     function getRecordControllerClass($model) {
 202         $models = $this->getManagedModels();
 203         
 204         if(isset($models[$model]['record_controller'])) {
 205             $class = $models[$model]['record_controller'];
 206         } else {
 207             $class = $this->stat('record_controller_class');
 208         }
 209         
 210         return $class;
 211     }
 212     
 213     /**
 214      * Add mappings for generic form constructors to automatically delegate to a scaffolded form object.
 215      */
 216     function defineMethods() {
 217         parent::defineMethods();
 218         foreach($this->getManagedModels() as $class => $options) {
 219             if(is_numeric($class)) $class = $options;
 220             $this->addWrapperMethod($class, 'bindModelController');
 221             self::$allowed_actions[] = $class;
 222         }
 223     }
 224     
 225     /**
 226      * Base scaffolding method for returning a generic model instance.
 227      */
 228     public function bindModelController($model, $request = null) {
 229         $class = $this->getCollectionControllerClass($model);   
 230         return new $class($this, $model);
 231     }
 232     
 233     /**
 234      * Returns all languages with languages already used appearing first.
 235      * Called by the SSViewer when rendering the template.
 236      */
 237     function LangSelector() {
 238         $member = Member::currentUser(); 
 239         $dropdown = new LanguageDropdownField(
 240             'LangSelector', 
 241             'Language', 
 242             array(),
 243             'SiteTree', 
 244             'Locale-English',
 245             singleton('SiteTree')
 246         );
 247         $dropdown->setValue(Translatable::get_current_locale());
 248         return $dropdown;
 249     }
 250     
 251     /**
 252      * @return boolean
 253      */
 254     function IsTranslatableEnabled($model=false) {
 255         if ($model) {
 256             return SS_Object::has_extension($model, 'Translatable');
 257         }
 258         if (count($this->getManagedModels()) == 1) {
 259             if (!is_numeric(array_shift(array_keys($this->getManagedModels())))) {
 260                 return SS_Object::has_extension(array_shift(array_keys($this->getManagedModels())), 'Translatable');
 261             } else {
 262                 return SS_Object::has_extension(array_shift($this->getManagedModels()), 'Translatable');
 263             }
 264         }       
 265         return false;
 266     }
 267     
 268     /**
 269      * This method can be overloaded to specify the UI by which the search class is chosen.
 270      *
 271      * It can create a tab strip or a dropdown.  The dropdown is useful when there are a large number of classes.
 272      * By default, it will show a tabs for 1-3 classes, and a dropdown for 4 or more classes.
 273      *
 274      * @return String: 'tabs' or 'dropdown'
 275      */
 276     public function SearchClassSelector() {
 277         return sizeof($this->getManagedModels()) > 2 ? 'dropdown' : 'tabs';
 278     }
 279     
 280     /**
 281      * Returns managed models' create, search, and import forms
 282      * @uses SearchContext
 283      * @uses SearchFilter
 284      * @return DataObjectSet of forms 
 285      */
 286     protected function getModelForms() {
 287         $models = $this->getManagedModels();
 288         $forms  = new DataObjectSet();
 289         
 290         foreach($models as $class => $options) { 
 291             if(is_numeric($class)) $class = $options;
 292             $forms->push(new ArrayData(array (
 293                 'Title'     => (is_array($options) && isset($options['title'])) ? $options['title'] : singleton($class)->i18n_plural_name(),
 294                 'ClassName' => $class,
 295                 'Content'   => $this->$class()->getModelSidebar()
 296             )));
 297         }
 298         
 299         return $forms;
 300     }
 301     
 302     /**
 303      * @return array
 304      */
 305     function getManagedModels() {
 306         $models = $this->stat('managed_models');
 307         if(is_string($models)) {
 308             $models = array($models);
 309         }
 310         if(!count($models)) {
 311             user_error(
 312                 'ModelAdmin::getManagedModels(): 
 313                 You need to specify at least one DataObject subclass in public static $managed_models.
 314                 Make sure that this property is defined, and that its visibility is set to "public"', 
 315                 E_USER_ERROR
 316             );
 317         }
 318         
 319         return $models;
 320     }
 321     
 322     /**
 323      * Returns all importers defined in {@link self::$model_importers}.
 324      * If none are defined, we fall back to {@link self::managed_models}
 325      * with a default {@link CsvBulkLoader} class. In this case the column names of the first row
 326      * in the CSV file are assumed to have direct mappings to properties on the object.
 327      *
 328      * @return array
 329      */
 330      function getModelImporters() {
 331         $importers = $this->stat('model_importers');
 332 
 333         // fallback to all defined models if not explicitly defined
 334         if(is_null($importers)) {
 335             $models = $this->getManagedModels();
 336             foreach($models as $modelName => $options) {
 337                 if(is_numeric($modelName)) $modelName = $options;
 338                 $importers[$modelName] = 'CsvBulkLoader';
 339             }
 340         }
 341         
 342         return $importers;
 343     }
 344     
 345 }
 346 
 347 /**
 348  * Handles a managed model class and provides default collection filtering behavior.
 349  *
 350  * @package cms
 351  * @subpackage core
 352  */
 353 class ModelAdmin_CollectionController extends Controller {
 354     public $parentController;
 355     protected $modelClass;
 356     
 357     static $url_handlers = array(
 358         '$Action' => 'handleActionOrID'
 359     );
 360 
 361     function __construct($parent, $model) {
 362         $this->parentController = $parent;
 363         $this->modelClass = $model;
 364         
 365         parent::__construct();
 366     }
 367     
 368     /**
 369      * Appends the model class to the URL.
 370      *
 371      * @param string $action
 372      * @return string
 373      */
 374     function Link($action = null) {
 375         return $this->parentController->Link(Controller::join_links($this->modelClass, $action));
 376     }
 377     
 378     /**
 379      * Return the class name of the model being managed.
 380      *
 381      * @return unknown
 382      */
 383     function getModelClass() {
 384         return $this->modelClass;
 385     }
 386     
 387     /**
 388      * Delegate to different control flow, depending on whether the
 389      * URL parameter is a number (record id) or string (action).
 390      * 
 391      * @param unknown_type $request
 392      * @return unknown
 393      */
 394     function handleActionOrID($request) {
 395         if (is_numeric($request->param('Action'))) {
 396             return $this->handleID($request);
 397         } else {
 398             return $this->handleAction($request);
 399         }
 400     }
 401     
 402     /**
 403      * Delegate to the RecordController if a valid numeric ID appears in the URL
 404      * segment.
 405      *
 406      * @param SS_HTTPRequest $request
 407      * @return RecordController
 408      */
 409     public function handleID($request) {
 410         $class = $this->parentController->getRecordControllerClass($this->getModelClass());
 411         return new $class($this, $request);
 412     }
 413     
 414     // -----------------------------------------------------------------------------------------------------------------
 415     
 416     /**
 417      * Get a combination of the Search, Import and Create forms that can be inserted into a {@link ModelAdmin} sidebar.
 418      *
 419      * @return string
 420      */
 421     public function getModelSidebar() {
 422         return $this->renderWith('ModelSidebar');
 423     }
 424     
 425     /**
 426      * Get a search form for a single {@link DataObject} subclass.
 427      * 
 428      * @return Form
 429      */
 430     public function SearchForm() {
 431         $context = singleton($this->modelClass)->getDefaultSearchContext();
 432         $fields = $context->getSearchFields();
 433         if ($fields) {
 434             foreach($fields as $field) {
 435                 if ($field->is_a('DropdownField')) { // delete empty fields (sublacces of DropdownField)
 436                     if (!$field->getSource()) {
 437                         $fields->removeByName($field->Name());
 438                     } else {
 439                         $dbObj = singleton($this->modelClass)->dbObject($field->Name());
 440                         if ($dbObj->is_a('Enum')) {
 441                             $map = array();
 442                             if ($field->getHasEmptyDefault()) {
 443                                 $map[''] = $field->getEmptyString();
 444                             }
 445                             foreach($dbObj->enumValues() as $value) {
 446                                 $map[$value] = _t("{$this->modelClass}.".$field->Name()."_{$value}", $value);
 447                             }
 448                             $field->setSource($map);
 449                         }
 450                     }
 451                 }
 452             }
 453         }
 454         $columnSelectionField = $this->ColumnSelectionField();
 455         $fields->push($columnSelectionField);
 456         if ($this->parentController->IsTranslatableEnabled($this->modelClass)) {
 457             $fields->push(new HiddenField('Locale','Locale', Translatable::get_current_locale()));
 458         }
 459         $validator = new RequiredFields();
 460         $validator->setJavascriptValidationHandler('none');
 461         
 462         $form = new Form($this, "SearchForm",
 463             $fields,
 464             new FieldSet(
 465                 new FormAction('search', _t('MemberTableField.SEARCH', 'Search')),
 466                 $clearAction = new ResetFormAction('clearsearch', _t('ModelAdmin.CLEAR_SEARCH','Clear Search'))
 467             ),
 468             $validator
 469         );
 470         //$form->setFormAction(Controller::join_links($this->Link(), "search"));
 471         $form->setFormMethod('get');
 472         $form->setHTMLID("Form_SearchForm_" . $this->modelClass);
 473         $form->disableSecurityToken();
 474         $clearAction->useButtonTag = true;
 475         $clearAction->addExtraClass('minorAction');
 476 
 477         return $form;
 478     }
 479     
 480     /**
 481      * Create a form that consists of one button 
 482      * that directs to a give model's Add form
 483      */ 
 484     public function CreateForm() {
 485         $modelName = $this->modelClass;
 486 
 487         if($this->hasMethod('alternatePermissionCheck')) {
 488             if(!$this->alternatePermissionCheck()) return false;
 489         } else {
 490             if(!singleton($modelName)->canCreate(Member::currentUser())) return false;
 491         }
 492         
 493         $buttonLabel = sprintf(_t('ModelAdmin.CREATEBUTTON', "Create '%s'", PR_MEDIUM, "Create a new instance from a model class"), singleton($modelName)->i18n_singular_name());
 494 
 495         $fields = new FieldSet();
 496         if ($this->parentController->IsTranslatableEnabled($modelName)) {
 497             $fields->push(new HiddenField('Locale','Locale', Translatable::get_current_locale()));
 498         }
 499         
 500         $form = new Form($this, "CreateForm",
 501                         $fields,
 502                         new FieldSet($createButton = new FormAction('add', $buttonLabel)),
 503                         $validator = new RequiredFields()
 504                 );
 505     
 506         $createButton->dontEscape = true;
 507         $validator->setJavascriptValidationHandler('none');
 508         $form->setHTMLID("Form_CreateForm_" . $this->modelClass);
 509         return $form;
 510     }
 511     
 512     /**
 513      * Generate a CSV import form for a single {@link DataObject} subclass.
 514      *
 515      * @return Form
 516      */
 517     public function ImportForm() {
 518         $modelName = $this->modelClass;
 519         $importers = $this->parentController->getModelImporters();
 520         if(!$importers || !isset($importers[$modelName])) return false;
 521         
 522         if(!singleton($modelName)->canCreate(Member::currentUser())) return false;
 523 
 524         $fields = new FieldSet(
 525             new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $modelName),
 526             new FileField('_CsvFile', false)
 527         );
 528         
 529         // get HTML specification for each import (column names etc.)
 530         $importerClass = $importers[$modelName];
 531         $importer = new $importerClass($modelName);
 532         $spec = $importer->getImportSpec();
 533         $specFields = new DataObjectSet();
 534         foreach($spec['fields'] as $name => $desc) {
 535             $specFields->push(new ArrayData(array('Name' => $name, 'Description' => $desc)));
 536         }
 537         $specRelations = new DataObjectSet();
 538         foreach($spec['relations'] as $name => $desc) {
 539             $specRelations->push(new ArrayData(array('Name' => $name, 'Description' => $desc)));
 540         }
 541         $specHTML = $this->customise(array(
 542             'ModelName' => Convert::raw2att($modelName),
 543             'ModelTitle' => singleton($modelName)->i18n_singular_name(),
 544             'Fields' => $specFields,
 545             'Relations' => $specRelations, 
 546         ))->renderWith('ModelAdmin_ImportSpec');
 547         
 548         $fields->push(new LiteralField("SpecFor{$modelName}", $specHTML));
 549         $fields->push(new CheckboxField('EmptyBeforeImport', _t('ModelAdmin.CLEAR_DB', 'Clear Database before import'), true)); 
 550         
 551         $actions = new FieldSet(
 552             new FormAction('import', _t('ModelAdmin.IMPORT', 'Import from CSV'))
 553         );
 554         
 555         $validator = new RequiredFields();
 556         $validator->setJavascriptValidationHandler('none');
 557         
 558         $form = new Form(
 559             $this,
 560             "ImportForm",
 561             $fields,
 562             $actions,
 563             $validator
 564         );
 565         $form->setHTMLID("Form_ImportForm");
 566         return $form;
 567     }
 568     
 569     /**
 570      * Imports the submitted CSV file based on specifications given in
 571      * {@link self::model_importers}.
 572      * Redirects back with a success/failure message.
 573      * 
 574      * @todo Figure out ajax submission of files via jQuery.form plugin
 575      *
 576      * @param array $data
 577      * @param Form $form
 578      * @param SS_HTTPRequest $request
 579      */
 580     function import($data, $form, $request) {
 581         $modelName = $data['ClassName'];
 582         $importers = $this->parentController->getModelImporters();
 583         $importerClass = $importers[$modelName];
 584         
 585         $loader = new $importerClass($data['ClassName']);
 586 
 587                 // File wasn't properly uploaded, show a reminder to the user
 588         if(empty($_FILES['_CsvFile']['tmp_name'])) {
 589             $form->sessionMessage(_t('ModelAdmin.NOCSVFILE', 'Please browse for a CSV file to import'), 'good');
 590             Director::redirectBack();
 591             return false;
 592         }
 593 
 594         if (!empty($data['EmptyBeforeImport']) && $data['EmptyBeforeImport']) { //clear database before import 
 595             $loader->deleteExistingRecords = true;
 596         }
 597         $results = $loader->load($_FILES['_CsvFile']['tmp_name']);
 598         
 599         $message = '';
 600         if($results->CreatedCount()) $message .= sprintf(
 601             _t('ModelAdmin.IMPORTEDRECORDS', "Imported %s records."), 
 602             $results->CreatedCount()
 603         );
 604         if($results->UpdatedCount()) $message .= sprintf(
 605             _t('ModelAdmin.UPDATEDRECORDS', "Updated %s records."),
 606             $results->UpdatedCount()
 607         );
 608         if($results->DeletedCount()) $message .= sprintf(
 609             _t('ModelAdmin.DELETEDRECORDS', "Deleted %s records."),
 610             $results->DeletedCount()
 611         );
 612         if(!$results->CreatedCount() && !$results->UpdatedCount()) $message .= _t('ModelAdmin.NOIMPORT', "Nothing to import");
 613         
 614         $form->sessionMessage($message, 'good');
 615         Director::redirectBack();
 616     }
 617     
 618     /**
 619      * Return the columns available in the column selection field.
 620      * Overload this to make other columns available
 621      */
 622     public function columnsAvailable() {
 623         return singleton($this->modelClass)->summaryFields();
 624     }
 625 
 626     /**
 627      * Return the columns selected by default in the column selection field.
 628      * Overload this to make other columns selected by default
 629      */
 630     public function columnsSelectedByDefault() {
 631         return array_keys(singleton($this->modelClass)->summaryFields());
 632     }
 633     
 634     /**
 635      * Give the flexibilility to show variouse combination of columns in the search result table
 636      */
 637     public function ColumnSelectionField() {
 638         $model = singleton($this->modelClass);
 639         $source = $this->columnsAvailable();
 640         
 641         // select all fields by default
 642         $value = $this->columnsSelectedByDefault();
 643         
 644         // Reorder the source so that you read items down the column and then across
 645         $columnisedSource = array();
 646         $keys = array_keys($source);
 647         $midPoint = ceil(sizeof($source)/2);
 648         for($i=0;$i<$midPoint;$i++) {
 649             $key1 = $keys[$i];
 650             $columnisedSource[$key1] = $model->fieldLabel($source[$key1]);
 651             // If there are an odd number of items, the last item will be unset
 652             if(isset($keys[$i+$midPoint])) {
 653                 $key2 = $keys[$i+$midPoint];
 654                 $columnisedSource[$key2] = $model->fieldLabel($source[$key2]);
 655             }
 656         }
 657 
 658         $checkboxes = new CheckboxSetField("ResultAssembly", false, $columnisedSource, $value);
 659         
 660         $field = new CompositeField(
 661             new LiteralField(
 662                 "ToggleResultAssemblyLink", 
 663                 sprintf("<a class=\"form_frontend_function toggle_result_assembly\" href=\"#\">%s</a>",
 664                     _t('ModelAdmin.CHOOSE_COLUMNS', 'Select result columns...')
 665                 )
 666             ),
 667             $checkboxesBlock = new CompositeField(
 668                 $checkboxes,
 669                 new LiteralField("ClearDiv", "<div class=\"clear\"></div>"),
 670                 new LiteralField(
 671                     "TickAllAssemblyLink",
 672                     sprintf(
 673                         "<a class=\"form_frontend_function tick_all_result_assembly\" href=\"#\">%s</a>",
 674                         _t('ModelAdmin.SELECTALL', 'select all')
 675                     )
 676                 ),
 677                 new LiteralField(
 678                     "UntickAllAssemblyLink",
 679                     sprintf(
 680                         "<a class=\"form_frontend_function untick_all_result_assembly\" href=\"#\">%s</a>",
 681                         _t('ModelAdmin.SELECTNONE', 'select none')
 682                     )
 683                 )
 684             )
 685         );
 686         
 687         $field->addExtraClass("ResultAssemblyBlock");
 688         $checkboxesBlock->addExtraClass("hidden");
 689         return $field;
 690     }
 691     
 692     /**
 693      * Action to render a data object collection, using the model context to provide filters
 694      * and paging.
 695      * 
 696      * @return string
 697      */
 698     function search($request, $form) {
 699         // Get the results form to be rendered
 700         $resultsForm = $this->ResultsForm(array_merge($form->getData(), $request));
 701         // Before rendering, let's get the total number of results returned
 702         $tableField = $resultsForm->Fields()->fieldByName($this->modelClass);
 703         $numResults = $tableField->TotalCount();
 704         
 705         if($numResults) {
 706             return new SS_HTTPResponse(
 707                 $resultsForm->forTemplate(), 
 708                 200, 
 709                 sprintf(
 710                     _t('ModelAdmin.FOUNDRESULTS',"Your search found %s matching items"), 
 711                     $numResults
 712                 )
 713             );
 714         } else {
 715             return new SS_HTTPResponse(
 716                 $resultsForm->forTemplate(), 
 717                 200, 
 718                 _t('ModelAdmin.NORESULTS',"Your search didn't return any matching items")
 719             );
 720         }
 721     }
 722     
 723     /**
 724      * Gets the search query generated on the SearchContext from
 725      * {@link DataObject::getDefaultSearchContext()},
 726      * and the current GET parameters on the request.
 727      *
 728      * @return SQLQuery
 729      */
 730     function getSearchQuery($searchCriteria) {
 731         $context = singleton($this->modelClass)->getDefaultSearchContext();
 732         return $context->getQuery($searchCriteria);
 733     }
 734     
 735     /**
 736      * Returns all columns used for tabular search results display.
 737      * Defaults to all fields specified in {@link DataObject->summaryFields()}.
 738      * 
 739      * @param array $searchCriteria Limit fields by populating the 'ResultsAssembly' key
 740      * @param boolean $selectedOnly Limit by 'ResultsAssempty
 741      */
 742     function getResultColumns($searchCriteria, $selectedOnly = true) {
 743         $model = singleton($this->modelClass);
 744 
 745         $summaryFields = $this->columnsAvailable();
 746         
 747         if($selectedOnly && isset($searchCriteria['ResultAssembly'])) {
 748             $resultAssembly = $searchCriteria['ResultAssembly'];
 749             if(!is_array($resultAssembly)) {
 750                 $explodedAssembly = preg_split('! *, *!', $resultAssembly);
 751                 $resultAssembly = array();
 752                 foreach($explodedAssembly as $item) $resultAssembly[$item] = true;
 753             }
 754             return array_intersect_key($summaryFields, $resultAssembly);
 755         } else {
 756             return $summaryFields;
 757         }
 758     }
 759     
 760     /**
 761      * Creates and returns the result table field for resultsForm.
 762      * Uses {@link resultsTableClassName()} to initialise the formfield. 
 763      * Method is called from {@link ResultsForm}.
 764      *
 765      * @param array $searchCriteria passed through from ResultsForm 
 766      *
 767      * @return TableListField 
 768      */
 769     function getResultsTable($searchCriteria) {
 770         
 771         $summaryFields = $this->getResultColumns($searchCriteria);
 772 
 773         $className = $this->parentController->resultsTableClassName();
 774         $tf = new $className(
 775             $this->modelClass,
 776             $this->modelClass,
 777             $summaryFields
 778         );
 779 
 780         $tf->setCustomQuery($this->getSearchQuery($searchCriteria));
 781         $tf->setPageSize($this->parentController->stat('page_length'));
 782         $tf->setShowPagination(true);
 783         // @todo Remove records that can't be viewed by the current user
 784         $tf->setPermissions(array_merge(array('view','export'), TableListField::permissions_for_object($this->modelClass)));
 785 
 786         // csv export settings (select all columns regardless of user checkbox settings in 'ResultsAssembly')
 787         $exportFields = $this->getResultColumns($searchCriteria, false);
 788         $tf->setFieldListCsv($exportFields);
 789 
 790         $editLink = $this->Link() . '/$ID/edit';
 791         if ($this->parentController->IsTranslatableEnabled($this->modelClass)) {
 792             $editLink .= '?Locale=' . Translatable::get_current_locale();
 793         }
 794         
 795         $url = '<a href=\"' . $editLink . '\">$value</a>';
 796         
 797         $tf->setFieldFormatting(array_combine(array_keys($summaryFields), array_fill(0,count($summaryFields), $url)));
 798     
 799         return $tf;
 800     }
 801     
 802     /**
 803      * Shows results from the "search" action in a TableListField. 
 804      *
 805      * @uses getResultsTable()
 806      *
 807      * @return Form
 808      */
 809     function ResultsForm($searchCriteria) {
 810         
 811         if($searchCriteria instanceof SS_HTTPRequest) $searchCriteria = $searchCriteria->getVars();
 812         
 813         $tf = $this->getResultsTable($searchCriteria);
 814         
 815         // implemented as a form to enable further actions on the resultset
 816         // (serverside sorting, export as CSV, etc)
 817         $form = new Form(
 818             $this,
 819             'ResultsForm',
 820             new FieldSet(
 821                 new HeaderField('SearchResultsHeader',_t('ModelAdmin.SEARCHRESULTS','Search Results'), 2),
 822                 $tf
 823             ),
 824             new FieldSet(
 825                 new FormAction("goBack", _t('ModelAdmin.GOBACK', "Back")),
 826                 new FormAction("goForward", _t('ModelAdmin.GOFORWARD', "Forward"))
 827             )
 828         );
 829         
 830         // Include the search criteria on the results form URL, but not dodgy variables like those below
 831         $filteredCriteria = $searchCriteria;
 832         unset($filteredCriteria['ctf']);
 833         unset($filteredCriteria['url']);
 834         unset($filteredCriteria['action_search']);
 835 
 836         $form->setFormAction($this->Link() . '/ResultsForm?' . http_build_query($filteredCriteria));
 837         return $form;
 838     }
 839     
 840 
 841     /////////////////////////////////////////////////////////////////////////////////////////////////////////
 842     
 843     /**
 844      * Create a new model record.
 845      *
 846      * @param unknown_type $request
 847      * @return unknown
 848      */
 849     function add($request) {
 850         return new SS_HTTPResponse(
 851             $this->AddForm()->forAjaxTemplate(), 
 852             200, 
 853             sprintf(
 854                 _t('ModelAdmin.ADDFORM', "Fill out this form to add a %s to the database."),
 855                 $this->modelClass
 856             )
 857         );
 858     }
 859 
 860     /**
 861      * Returns a form suitable for adding a new model, falling back on the default edit form.
 862      * 
 863      * Caution: The add-form shows a DataObject's {@link DataObject->getCMSFields()} method on a record
 864      * that doesn't exist in the database yet, hence has no ID. This means the {@link DataObject->getCMSFields()}
 865      * implementation has to ensure that no fields are added which would rely on a
 866      * record ID being present, e.g. {@link HasManyComplexTableField}.
 867      * 
 868      * Example:
 869      * <code>
 870      * function getCMSFields() {
 871      *   $fields = parent::getCMSFields();
 872      *     if($this->exists()) {
 873      *       $ctf = new HasManyComplexTableField($this, 'MyRelations', 'MyRelation');
 874      *       $fields->addFieldToTab('Root.Main', $ctf);
 875      *     }
 876      *   return $fields;
 877      * }
 878      * </code>
 879      *
 880      * @return Form
 881      */
 882     public function AddForm() {
 883         $newRecord = new $this->modelClass();
 884         
 885         if($newRecord->canCreate()){
 886             if($newRecord->hasMethod('getCMSAddFormFields')) {
 887                 $fields = $newRecord->getCMSAddFormFields();
 888             } else {
 889                 $fields = $newRecord->getCMSFields();
 890             }
 891             
 892             $validator = ($newRecord->hasMethod('getCMSValidator')) ? $newRecord->getCMSValidator() : null;
 893             if(!$validator) $validator = new RequiredFields();
 894             $validator->setJavascriptValidationHandler('none');
 895             
 896             $actions = new FieldSet (
 897                 new FormAction("doCreate", _t('ModelAdmin.ADDBUTTON', "Add"))
 898             );
 899             
 900             $form = new Form($this, "AddForm", $fields, $actions, $validator);
 901             $form->loadDataFrom($newRecord);
 902             
 903             if ($this->parentController->IsTranslatableEnabled($this->modelClass) && ($localeField = $fields->dataFieldByName('Locale'))) {
 904                 $localeField->setValue(Translatable::get_current_locale());
 905             }
 906             return $form;
 907         }
 908     }
 909     
 910     function doCreate($data, $form, $request) {
 911         $className = $this->getModelClass();
 912         $model = new $className();
 913         // We write before saveInto, since this will let us save has-many and many-many relationships :-)
 914         $model->write();
 915         $form->saveInto($model);
 916         $model->write();
 917         
 918         if(Director::is_ajax()) {
 919             $class = $this->parentController->getRecordControllerClass($this->getModelClass());
 920             $recordController = new $class($this, $request, $model->ID);
 921             return new SS_HTTPResponse(
 922                 $recordController->EditForm()->forAjaxTemplate(), 
 923                 200, 
 924                 sprintf(
 925                     _t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
 926                     $model->Title
 927                 )
 928             );
 929         } else {
 930             Director::redirect(Controller::join_links($this->Link(), $model->ID , 'edit'));
 931         }
 932     }
 933 }
 934 
 935 /**
 936  * Handles operations on a single record from a managed model.
 937  * 
 938  * @package cms
 939  * @subpackage core
 940  * @todo change the parent controller varname to indicate the model scaffolding functionality in ModelAdmin
 941  */
 942 class ModelAdmin_RecordController extends Controller {
 943     protected $parentController;
 944     protected $currentRecord;
 945     
 946     static $allowed_actions = array('edit', 'view', 'EditForm', 'ViewForm');
 947     
 948     function __construct($parentController, $request, $recordID = null) {
 949         $this->parentController = $parentController;
 950         $modelName = $parentController->getModelClass();
 951         $recordID = ($recordID) ? $recordID : $request->param('Action');
 952         $this->currentRecord = DataObject::get_by_id($modelName, $recordID);
 953         
 954         parent::__construct();
 955     }
 956     
 957     /**
 958      * Link fragment - appends the current record ID to the URL.
 959      */
 960     public function Link($action = null) {
 961         return $this->parentController->Link(Controller::join_links($this->currentRecord->ID, $action));
 962     }
 963 
 964     /////////////////////////////////////////////////////////////////////////////////////////////////////////
 965     
 966     /**
 967      * Edit action - shows a form for editing this record
 968      */
 969     function edit($request) {
 970         if ($this->currentRecord) {
 971             if(Director::is_ajax()) {
 972                 return new SS_HTTPResponse(
 973                     $this->EditForm()->forAjaxTemplate(), 
 974                     200, 
 975                     sprintf(
 976                         _t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
 977                         $this->currentRecord->Title
 978                     )
 979                 );
 980             } else {
 981                 // This is really quite ugly; to fix will require a change in the way that customise() works. :-(
 982                 return $this->parentController->parentController->customise(array(
 983                     'Right' => $this->parentController->parentController->customise(array(
 984                         'EditForm' => $this->EditForm()
 985                     ))->renderWith('ModelAdmin_right')
 986                 ))->renderWith(array('ModelAdmin','LeftAndMain'));
 987                 return ;
 988             }
 989         } else {
 990             return _t('ModelAdmin.ITEMNOTFOUND', "I can't find that item");
 991         }
 992     }
 993 
 994     /**
 995      * Returns a form for editing the attached model
 996      */
 997     public function EditForm() {
 998         $fields = $this->currentRecord->getCMSFields();
 999         $fields->push(new HiddenField("ID"));
1000         
1001         $validator = ($this->currentRecord->hasMethod('getCMSValidator')) ? $this->currentRecord->getCMSValidator() : new RequiredFields();
1002         $validator->setJavascriptValidationHandler('none');
1003         
1004         $actions = $this->currentRecord->getCMSActions();
1005         if($this->currentRecord->canEdit(Member::currentUser())){
1006             $actions->push(new FormAction("doSave", _t('ModelAdmin.SAVE', "Save")));
1007         }else{
1008             $fields = $fields->makeReadonly();
1009         }
1010         
1011         if($this->currentRecord->canDelete(Member::currentUser())) {
1012             $actions->insertFirst($deleteAction = new FormAction('doDelete', _t('ModelAdmin.DELETE', 'Delete')));
1013             $deleteAction->addExtraClass('delete');
1014         }
1015 
1016         $actions->insertFirst(new FormAction("goBack", _t('ModelAdmin.GOBACK', "Back")));
1017         
1018         $form = new Form($this, "EditForm", $fields, $actions, $validator);
1019         $form->loadDataFrom($this->currentRecord);
1020 
1021         return $form;
1022     }
1023 
1024     /**
1025      * Postback action to save a record
1026      *
1027      * @param array $data
1028      * @param Form $form
1029      * @param SS_HTTPRequest $request
1030      * @return mixed
1031      */
1032     function doSave($data, $form, $request) {
1033         $form->saveInto($this->currentRecord);
1034         
1035         try {
1036             $this->currentRecord->write();
1037         } catch(ValidationException $e) {
1038             $form->sessionMessage($e->getResult()->message(), 'bad');
1039         }
1040         
1041         
1042         // Behaviour switched on ajax.
1043         if(Director::is_ajax()) {
1044             return $this->edit($request);
1045         } else {
1046             Director::redirectBack();
1047         }
1048     }   
1049     
1050     /**
1051      * Delete the current record
1052      */
1053     public function doDelete($data, $form, $request) {
1054         if($this->currentRecord->canDelete(Member::currentUser())) {
1055             $this->currentRecord->delete();
1056             Director::redirect($this->parentController->Link('SearchForm?action=search'));
1057         }
1058         else Director::redirectBack();
1059         return;
1060     }
1061     
1062     /////////////////////////////////////////////////////////////////////////////////////////////////////////
1063 
1064     /**
1065      * Renders the record view template.
1066      * 
1067      * @param SS_HTTPRequest $request
1068      * @return mixed
1069      */
1070     function view($request) {
1071         if($this->currentRecord) {
1072             $form = $this->ViewForm();
1073             return $form->forAjaxTemplate();
1074         } else {
1075             return _t('ModelAdmin.ITEMNOTFOUND');
1076         }
1077     }
1078 
1079     /**
1080      * Returns a form for viewing the attached model
1081      * 
1082      * @return Form
1083      */
1084     public function ViewForm() {
1085         $fields = $this->currentRecord->getCMSFields();
1086         $form = new Form($this, "EditForm", $fields, new FieldSet());
1087         $form->loadDataFrom($this->currentRecord);
1088         $form->makeReadonly();
1089         return $form;
1090     }
1091     
1092     /////////////////////////////////////////////////////////////////////////////////////////////////////////
1093 
1094     function index() {
1095         Director::redirect(Controller::join_links($this->Link(), 'edit'));
1096     }
1097     
1098     function getCurrentRecord(){
1099         return $this->currentRecord;
1100     }
1101     
1102 }
1103 
1104 ?>
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0