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

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • 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

  • BigFilesReport
  • BrokenLinksReport
  • CMSMain
  • CMSMainMarkingFilter
  • CMSMenu
  • CMSMenuItem
  • CMSSiteTreeFilter
  • CMSSiteTreeFilter_ChangedPages
  • CMSSiteTreeFilter_DeletedPages
  • CMSSiteTreeFilter_Search
  • NonUsedFilesReport
  • RedirectorPage
  • RedirectorPage_Controller
  • SideReport_BrokenFiles
  • SideReport_BrokenLinks
  • SideReport_BrokenRedirectorPages
  • SideReport_BrokenVirtualPages
  • SideReport_EmptyPages
  • SideReport_RecentlyEdited
  • SideReport_ToDo
  • SideReportView
  • SideReportWrapper
  • SilverStripeNavigator
  • SilverStripeNavigatorItem
  • SilverStripeNavigatorItem_ArchiveLink
  • SilverStripeNavigatorItem_CMSLink
  • SilverStripeNavigatorItem_LiveLink
  • SilverStripeNavigatorItem_StageLink
  • WidgetAreaEditor
   1 <?php
   2 /**
   3  * The main "content" area of the CMS.
   4  * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
   5  * admin menu.
   6  * 
   7  * @package cms
   8  * @subpackage content
   9  * @todo Create some base classes to contain the generic functionality that will be replicated.
  10  */
  11 class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
  12     
  13     static $url_segment = '';
  14     
  15     static $url_rule = '/$Action/$ID/$OtherID';
  16     
  17     // Maintain a lower priority than other administration sections
  18     // so that Director does not think they are actions of CMSMain
  19     static $url_priority = 40;
  20     
  21     static $menu_title = 'Pages';
  22     
  23     static $menu_priority = 10;
  24     
  25     static $tree_class = "SiteTree";
  26     
  27     static $subitem_class = "Member";
  28     
  29     static $allowed_actions = array(
  30         'addmember',
  31         'addpage',
  32         'buildbrokenlinks',
  33         'canceldraftchangesdialog',
  34         'compareversions',
  35         'createtranslation',
  36         'delete',
  37         'deletefromlive',
  38         'deleteitems',
  39         'DeleteItemsForm',
  40         'dialog',
  41         'duplicate',
  42         'duplicatewithchildren',
  43         'getpagecount',
  44         'getversion',
  45         'publishall',
  46         'publishitems',
  47         'PublishItemsForm',
  48         'restore',
  49         'revert',
  50         'rollback',
  51         'sidereport',
  52         'submit',
  53         'unpublish',
  54         'versions',
  55         'EditForm',
  56         'AddPageOptionsForm',
  57         'SiteTreeAsUL',
  58         'getshowdeletedsubtree',
  59         'getfilteredsubtree',
  60         'batchactions'
  61     );
  62     
  63     /**
  64      * SiteTree Columns that can be filtered using the the Site Tree Search button
  65      */
  66     static function T_SiteTreeFilterOptions(){
  67         return array(
  68             'Title' => _t('CMSMain.TITLEOPT', 'Title', 0, 'The dropdown title in CMSMain left SiteTreeFilterOptions'),
  69             'MenuTitle' => _t('CMSMain.MENUTITLEOPT', 'Navigation Label', 0, 'The dropdown title in CMSMain left SiteTreeFilterOptions'),
  70             'ClassName' => _t('CMSMain.PAGETYPEOPT', 'Page Type'), 
  71             'Status' => _t('CMSMain.STATUSOPT', 'Status',  0, "The dropdown title in CMSMain left SiteTreeFilterOptions"), 
  72             'MetaDescription' => _t('CMSMain.METADESCOPT', 'Description', 0, "The dropdown title in CMSMain left SiteTreeFilterOptions"), 
  73             'MetaKeywords' => _t('CMSMain.METAKEYWORDSOPT', 'Keywords', 0, "The dropdown title in CMSMain left SiteTreeFilterOptions")
  74         );
  75     }
  76     
  77     public function init() {
  78         parent::init();
  79         
  80         // Locale" attribute is either explicitly added by LeftAndMain Javascript logic,
  81         // or implied on a translated record (see {@link Translatable->updateCMSFields()}).
  82         // $Lang serves as a "context" which can be inspected by Translatable - hence it
  83         // has the same name as the database property on Translatable.
  84         if($this->getRequest()->requestVar("Locale")) {
  85             $this->Locale = $this->getRequest()->requestVar("Locale");
  86         } elseif($this->getRequest()->requestVar("locale")) {
  87             $this->Locale = $this->getRequest()->requestVar("locale");
  88         } else {
  89             $this->Locale = Translatable::default_locale();
  90         }
  91         Translatable::set_current_locale($this->Locale);
  92         
  93         // collect languages for TinyMCE spellchecker plugin.
  94         // see http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker
  95         $langName = i18n::get_locale_name($this->Locale);
  96         HtmlEditorConfig::get('cms')->setOption('spellchecker_languages', "+{$langName}={$this->Locale}");
  97                 
  98         Requirements::javascript(CMS_DIR . '/javascript/CMSMain.js');
  99         Requirements::javascript(CMS_DIR . '/javascript/CMSMain_left.js');
 100         Requirements::javascript(CMS_DIR . '/javascript/CMSMain_right.js');
 101     }
 102     
 103     /**
 104      * If this is set to true, the "switchView" context in the
 105      * template is shown, with links to the staging and publish site.
 106      *
 107      * @return boolean
 108      */
 109     function ShowSwitchView() {
 110         return true;
 111     }
 112     
 113     /**
 114      * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
 115      * to switch view also for archived versions.
 116      */
 117     function SwitchView($page = null) {
 118         if(!$page) {
 119             $page = $this->currentPage();
 120         }
 121         
 122         if($page) {
 123             $nav = SilverStripeNavigator::get_for_record($page);
 124             Requirements::customScript("window.name = windowName('cms');");
 125             return $nav['items'];
 126         }
 127     }
 128 
 129     //------------------------------------------------------------------------------------------//
 130     // Main controllers
 131 
 132     //------------------------------------------------------------------------------------------//
 133     // Main UI components
 134 
 135     /**
 136      * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
 137      * 
 138      * @return string
 139      */
 140     public function Link($action = null) {
 141         return Controller::join_links(
 142             $this->stat('url_base', true),
 143             $this->stat('url_segment', true), // in case we want to change the segment
 144             '/', // trailing slash needed if $action is null!
 145             "$action"
 146         );
 147     }
 148 
 149     /**
 150      * Return the entire site tree as a nested set of ULs
 151      */
 152     public function SiteTreeAsUL() {
 153         $this->generateDataTreeHints();
 154         $this->generateTreeStylingJS();
 155 
 156         // Pre-cache sitetree version numbers for querying efficiency
 157         Versioned::prepopulate_versionnumber_cache("SiteTree", "Stage");
 158         Versioned::prepopulate_versionnumber_cache("SiteTree", "Live");
 159 
 160         return $this->getSiteTreeFor("SiteTree");
 161     }
 162     
 163     public function getCMSTreeTitle() {
 164         return SiteConfig::current_site_config()->i18n_singular_name();
 165     }
 166 
 167     /**
 168      * Use a CMSSiteTreeFilter to only get certain nodes
 169      *
 170      * @return string
 171      */
 172     public function getfilteredsubtree() {
 173         // Sanity and security checks
 174         if (!isset($_REQUEST['filter'])) die('No filter passed');
 175         if (!ClassInfo::exists($_REQUEST['filter'])) die ('That filter class does not exist');
 176         if (!is_subclass_of($_REQUEST['filter'], 'CMSSiteTreeFilter')) die ('That is not a valid filter');
 177         
 178         // Do eeet!
 179         $filter = new $_REQUEST['filter']();
 180         return $filter->getTree();
 181     }
 182     
 183     /**
 184      * Returns a list of batch actions
 185      */
 186     function SiteTreeFilters() {
 187         $filters = ClassInfo::subclassesFor('CMSSiteTreeFilter');
 188         array_shift($filters);
 189         $doSet = new DataObjectSet();
 190         $doSet->push(new ArrayData(array(
 191             'ClassName' => 'all',
 192             'Title' => _t('CMSSiteTreeFilter.ALL', 'All items')
 193         )));
 194         foreach($filters as $filter) {
 195             if (call_user_func(array($filter, 'showInList'))) {
 196                 $doSet->push(new ArrayData(array(
 197                     'ClassName' => $filter,
 198                     'Title' => call_user_func(array($filter, 'title'))
 199                 )));
 200             }
 201         }
 202         return $doSet;
 203     }
 204     
 205     /**
 206      * Returns the SiteTree columns that can be filtered using the the Site Tree Search button as a DataObjectSet
 207      */
 208     public function SiteTreeFilterOptions() {
 209         $filter_options = new DataObjectSet();
 210         foreach(self::T_SiteTreeFilterOptions() as $key => $value) {
 211             $record = array(
 212                 'Column' => $key,
 213                 'Title' => $value,
 214             );
 215             $filter_options->push(new ArrayData($record));
 216         }
 217         return $filter_options;
 218     }
 219     public function SiteTreeFilterDateField() {
 220         $dateField = new DateField('SiteTreeFilterDate');
 221         $dateField->setConfig('showcalendar', true);
 222         return $dateField->Field();
 223     }
 224     public function SiteTreeFilterPageTypeField() {
 225         $types = SiteTree::page_type_classes(); 
 226         $source = array_combine($types, $types); // FIXME use i18n names
 227         $source['All']= _t('CMSMain.AllClasses', 'All');
 228         ksort($source);
 229         $optionsetField = new DropdownField('ClassName', 'ClassName', $source, 'Any');
 230         return $optionsetField->Field();
 231     }
 232 
 233     public function generateDataTreeHints() { 
 234         $classes = ClassInfo::subclassesFor( $this->stat('tree_class') );
 235 
 236         $def['Root'] = array();
 237 
 238         foreach($classes as $class) {
 239             $obj = singleton($class);
 240             if($obj instanceof HiddenClass) continue;
 241 
 242             $allowedChildren = $obj->allowedChildren();
 243             if($allowedChildren != "none")  $def[$class]['allowedChildren'] = $allowedChildren;
 244             $def[$class]['defaultChild'] = $obj->defaultChild();
 245             $def[$class]['defaultParent'] = ($obj->defaultParent() && isset(SiteTree::get_by_link($obj->defaultParent())->ID)) ? SiteTree::get_by_link($obj->defaultParent())->ID : null;
 246 
 247             if(is_array($allowedChildren)) foreach($allowedChildren as $allowedChild) {
 248                 $def[$allowedChild]['allowedParents'][] = $class;
 249             }
 250 
 251             if($obj->stat('can_be_root')) {
 252                 $def['Root']['allowedChildren'][] = $class;
 253             }
 254         }
 255 
 256         // Put data hints into a script tag at the top
 257         Requirements::customScript("siteTreeHints = " . $this->jsDeclaration($def) . ";");
 258     }
 259 
 260     public function generateTreeStylingJS() {
 261         $classes = ClassInfo::subclassesFor('SiteTree');
 262         foreach($classes as $class) {
 263             $obj = singleton($class);
 264             if($obj instanceof HiddenClass) continue;
 265             if($icon = $obj->stat('icon')) $iconInfo[$class] = $icon;
 266         }
 267         $iconInfo['BrokenLink'] = 'cms/images/treeicons/brokenlink';
 268 
 269 
 270         $js = "var _TREE_ICONS = [];\n";
 271 
 272 
 273         foreach($iconInfo as $class => $icon) {
 274             // SiteTree::$icon can be set to array($icon, $option)
 275             // $option can be "file" or "folder" to force the icon to always be the file or the folder form
 276             $option = null;
 277             if(is_array($icon)) list($icon, $option) = $icon;
 278 
 279             $fileImage = ($option == "folder") ? $icon . '-closedfolder.gif' : $icon . '-file.gif';
 280             $openFolderImage = $icon . '-openfolder.gif';
 281             if(!Director::fileExists($openFolderImage) || $option == "file") $openFolderImage = $fileImage;
 282             $closedFolderImage = $icon . '-closedfolder.gif';
 283             if(!Director::fileExists($closedFolderImage) || $option == "file") $closedFolderImage = $fileImage;
 284 
 285             $js .= <<<JS
 286                 _TREE_ICONS['$class'] = {
 287                     fileIcon: '$fileImage',
 288                     openFolderIcon: '$openFolderImage',
 289                     closedFolderIcon: '$closedFolderImage'
 290                 };
 291 
 292 JS;
 293         }
 294 
 295         Requirements::customScript($js);
 296     }
 297 
 298     /**
 299      * Return a javascript instanciation of this array
 300      */
 301     protected function jsDeclaration($array) {
 302         $parts = array();
 303         if(is_array($array)) {
 304             $object = false;
 305             foreach(array_keys($array) as $key) {
 306                 if(!is_numeric($key)) {
 307                     $object = true;
 308                     break;
 309                 }
 310             }
 311 
 312             if($object) {
 313                 foreach($array as $k => $v) {
 314                     $parts[] = "$k : " . $this->jsDeclaration($v);
 315                 }
 316                 return " {\n " . implode(", \n", $parts) . " }\n";
 317             } else {
 318                 foreach($array as $part) $parts[] = $this->jsDeclaration($part);
 319                 return " [ " . implode(", ", $parts) . " ]\n";
 320             }
 321         } else {
 322             return "'" . addslashes($array) . "'";
 323         }
 324     }
 325 
 326     /**
 327      * Populates an array of classes in the CMS
 328      * which allows the user to change the page type.
 329      *
 330      * @return DataObjectSet
 331      */
 332     public function PageTypes() {
 333         $classes = SiteTree::page_type_classes();
 334 
 335         $result = new DataObjectSet();
 336 
 337         foreach($classes as $class) {
 338             $instance = singleton($class);
 339 
 340             if($instance instanceof HiddenClass) continue;
 341 
 342             if(!$instance->canCreate()) continue;
 343 
 344             // skip this type if it is restricted
 345             if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;
 346 
 347             $addAction = $instance->i18n_singular_name();
 348             
 349             // if we're in translation mode, the link between the translated pagetype
 350             // title and the actual classname might not be obvious, so we add it in parantheses
 351             // Example: class "RedirectorPage" has the title "Weiterleitung" in German,
 352             // so it shows up as "Weiterleitung (RedirectorPage)"
 353             if (Director::isDev()) {
 354                 $addAction .= " ({$class})";
 355             }
 356 
 357             $result->push(new ArrayData(array(
 358                 'ClassName' => $class,
 359                 'AddAction' => $addAction,
 360                 'Sort' => ($class == 'DocPage') ? '0' : $addAction, // DocPage first
 361             )));
 362         }
 363         
 364         $result->sort('Sort');
 365         
 366         return $result;
 367     }
 368 
 369     /**
 370      * Save the current sites {@link SiteConfig} into the database
 371      *
 372      * @param array $data 
 373      * @param Form $form 
 374      * @return FormResponse
 375      */
 376     function save_siteconfig($data, $form) {
 377         $siteConfig = SiteConfig::current_site_config();
 378         $form->saveInto($siteConfig);
 379         $siteConfig->write();
 380         FormResponse::status_message('Saved site configuration', "good");
 381         FormResponse::add("$('Form_EditForm').resetElements();");
 382 
 383         return FormResponse::respond();
 384     }
 385     /**
 386      * Get a database record to be managed by the CMS
 387      */
 388     public function getRecord($id) {
 389 
 390         $treeClass = $this->stat('tree_class');
 391 
 392         if($id && is_numeric($id)) {
 393             $record = DataObject::get_one( $treeClass, "\"$treeClass\".\"ID\" = $id");
 394 
 395             // Then, try getting a record from the live site
 396             if(!$record) {
 397                 // $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
 398                 Versioned::reading_stage('Live');
 399                 singleton($treeClass)->flushCache();
 400 
 401                 $record = DataObject::get_one( $treeClass, "\"$treeClass\".\"ID\" = $id");
 402                 if($record) Versioned::set_reading_mode('');
 403             }
 404             
 405             // Then, try getting a deleted record
 406             if(!$record) {
 407                 $record = Versioned::get_latest_version($treeClass, $id);
 408             }
 409 
 410             // Don't open a page from a different locale
 411             /** The record's Locale is saved in database in 2.4, and not related with Session,
 412              *  we should not check their locale matches the Translatable::get_current_locale,
 413              *  here as long as we all the HTTPRequest is init with right locale.
 414              *  This bit breaks the all FileIFrameField functions if the field is used in CMS
 415              *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField. 
 416              */
 417             /* if($record && Object::has_extension('SiteTree', 'Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
 418                 $record = null;
 419             }*/
 420 
 421             return $record;
 422 
 423         } else if(substr($id,0,3) == 'new') {
 424             return $this->getNewItem($id);
 425         }
 426     }
 427 
 428     public function getEditForm($id) {
 429         $record = $this->getRecord($id);
 430 
 431         if($record) {
 432             if($record->IsDeletedFromStage) $record->Status = _t('CMSMain.REMOVEDFD',"Removed from the draft site");
 433 
 434             $fields = $record->getCMSFields($this);
 435             if ($fields == null) {
 436                 user_error("getCMSFields returned null on a '".get_class($record)."' object - it should return a FieldSet object. Perhaps you forgot to put a return statement at the end of your method?", E_USER_ERROR);
 437             }
 438             $fields->push($idField = new HiddenField("ID"));
 439             $fields->push($liveURLField = new HiddenField("LiveURLSegment"));
 440             $fields->push($stageURLField = new HiddenField("StageURLSegment"));
 441 
 442             /*if( substr($record->ID, 0, 3 ) == 'new' )*/
 443             $fields->push(new HiddenField('Sort','', $record->Sort ));
 444 
 445             $idField->setValue($id);
 446             
 447             if($record->ID && is_numeric( $record->ID ) ) {
 448                 $liveRecord = Versioned::get_one_by_stage('SiteTree', 'Live', "\"SiteTree\".\"ID\" = $record->ID");
 449                 if($liveRecord) $liveURLField->setValue($liveRecord->AbsoluteLink());
 450             }
 451             
 452             if(!$record->IsDeletedFromStage) {
 453                 $stageURLField->setValue($record->AbsoluteLink());
 454             }
 455             
 456             // getAllCMSActions can be used to completely redefine the action list
 457             if($record->hasMethod('getAllCMSActions')) {
 458                 $actions = $record->getAllCMSActions();
 459             } else {
 460                 $actions = $record->getCMSActions();
 461             }
 462             
 463             // Add a default or custom validator.
 464             // @todo Currently the default Validator.js implementation
 465             //  adds javascript to the document body, meaning it won't
 466             //  be included properly if the associated fields are loaded
 467             //  through ajax. This means only serverside validation
 468             //  will kick in for pages+validation loaded through ajax.
 469             //  This will be solved by using less obtrusive javascript validation
 470             //  in the future, see http://open.silverstripe.com/ticket/2915 and http://open.silverstripe.com/ticket/3386
 471             if($record->hasMethod('getCMSValidator')) {
 472                 $validator = $record->getCMSValidator();
 473             } else {
 474                 $validator = new RequiredFields();
 475             }
 476             
 477             // The clientside (mainly LeftAndMain*.js) rely on ajax responses
 478             // which can be evaluated as javascript, hence we need
 479             // to override any global changes to the validation handler.
 480             $validator->setJavascriptValidationHandler('prototype');
 481             
 482             $form = new Form($this, "EditForm", $fields, $actions, $validator);
 483             $form->loadDataFrom($record);
 484             $form->disableDefaultAction();
 485 
 486             if(!$record->canEdit() || $record->IsDeletedFromStage) {
 487                 $readonlyFields = $form->Fields()->makeReadonly();
 488                 $form->setFields($readonlyFields);
 489             }
 490             
 491             $this->extend('updateEditForm', $form);
 492 
 493             return $form;
 494         } if ($id == 0 || $id == 'root') {
 495             return $this->RootForm();
 496         } else if($id) {
 497             return new Form($this, "EditForm", new FieldSet(
 498                 new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldSet());
 499 
 500         }
 501     }
 502 
 503     /**
 504      * @return Form
 505      */
 506     function RootForm() {
 507         $siteConfig = SiteConfig::current_site_config();
 508         $fields = $siteConfig->getCMSFields(); 
 509         if(Object::has_extension('SiteConfig',"Translatable")){ 
 510             $fields->push(new HiddenField('Locale','', $siteConfig->Locale ));       
 511         } 
 512         $form = new Form($this, "EditForm", $fields, $siteConfig->getCMSActions());
 513         $form->loadDataFrom($siteConfig);
 514         
 515         $this->extend('updateEditForm', $form);
 516 
 517         return $form;
 518     }
 519 
 520     //------------------------------------------------------------------------------------------//
 521     // Data saving handlers
 522 
 523 
 524     public function addpage() {
 525                 // FIX singleton error. Inxo
 526                 $className = 'DocPage';
 527                 if(isset($_REQUEST['PageType']) && $_REQUEST['PageType']!='' && class_exists($_REQUEST['PageType'])){
 528                     $className = $_REQUEST['PageType'];
 529                 }
 530         //$className = isset($_REQUEST['PageType']) ? $_REQUEST['PageType'] : "Page";
 531                 // end FIX
 532         $parent = isset($_REQUEST['ParentID']) ? $_REQUEST['ParentID'] : 0;
 533         $suffix = isset($_REQUEST['Suffix']) ? "-" . $_REQUEST['Suffix'] : null;
 534 
 535         if(!$parent && isset($_REQUEST['Parent'])) {
 536             $page = SiteTree::get_by_link($_REQUEST['Parent']);
 537             if($page) $parent = $page->ID;
 538         }
 539 
 540         if(is_numeric($parent)) $parentObj = DataObject::get_by_id("SiteTree", $parent);
 541         if(!$parentObj || !$parentObj->ID) $parent = 0;
 542         
 543         if($parentObj){
 544             if(!$parentObj->canAddChildren()) return Security::permissionFailure($this);
 545             if(!singleton($className)->canCreate()) return Security::permissionFailure($this);
 546         }else{
 547             if(!SiteConfig::current_site_config()->canCreateTopLevel())
 548                 return Security::permissionFailure($this);
 549         }
 550 
 551         $p = $this->getNewItem("new-$className-$parent".$suffix, false);
 552         $p->Locale = $_REQUEST['Locale'];
 553         $p->write();
 554 
 555         return $this->returnItemToUser($p);
 556     }
 557 
 558     /**
 559      * @uses LeftAndMainDecorator->augmentNewSiteTreeItem()
 560      */
 561     public function getNewItem($id, $setID = true) {
 562         list($dummy, $className, $parentID, $suffix) = array_pad(explode('-',$id),4,null);
 563         
 564         $newItem = new $className();
 565 
 566         if( !$suffix ) {
 567             $sessionTag = "NewItems." . $parentID . "." . $className;
 568             if(Session::get($sessionTag)) {
 569                 $suffix = '-' . Session::get($sessionTag);
 570                 Session::set($sessionTag, Session::get($sessionTag) + 1);
 571             }
 572             else
 573                 Session::set($sessionTag, 1);
 574 
 575                 $id = $id . $suffix;
 576         }
 577 
 578 //      if (!$newItem->Title) $newItem->Title = _t('CMSMain.NEW',"New ",PR_MEDIUM,'"New " followed by a className').singleton($className)->i18n_singular_name(); 
 579         if (!$newItem->Title) $newItem->Title = singleton($className)->i18n_singular_name(); 
 580         if (!$newItem->URLSegment) $newItem->URLSegment = "new-" . strtolower($className);
 581         $newItem->ClassName = $className;
 582         $newItem->ParentID = $parentID;
 583 
 584         // DataObject::fieldExists only checks the current class, not the hierarchy
 585         // This allows the CMS to set the correct sort value
 586         if($newItem->castingHelper('Sort')) {
 587             $newItem->Sort = DB::query("SELECT MAX(\"Sort\") FROM \"SiteTree\" WHERE \"ParentID\" = '" . Convert::raw2sql($parentID) . "'")->value() + 1;
 588         }
 589 
 590         if($setID) $newItem->ID = $id;
 591 
 592         # Some modules like subsites add extra fields that need to be set when the new item is created
 593         $this->extend('augmentNewSiteTreeItem', $newItem);
 594         
 595         return $newItem;
 596     }
 597 
 598     /**
 599      * Delete the page from live. This means a page in draft mode might still exist.
 600      * 
 601      * @see delete()
 602      */
 603     public function deletefromlive($urlParams, $form) {
 604         $id = $_REQUEST['ID'];
 605         Versioned::reading_stage('Live');
 606         $record = DataObject::get_by_id("SiteTree", $id);
 607         if($record && !($record->canDelete() && $record->canDeleteFromLive())) return Security::permissionFailure($this);
 608         
 609         $descRemoved = '';
 610         $descendantsRemoved = 0;
 611         
 612         // before deleting the records, get the descendants of this tree
 613         if($record) {
 614             $descendantIDs = $record->getDescendantIDList();
 615 
 616             // then delete them from the live site too
 617             $descendantsRemoved = 0;
 618             foreach( $descendantIDs as $descID )
 619                 if( $descendant = DataObject::get_by_id('SiteTree', $descID) ) {
 620                     $descendant->doDeleteFromLive();
 621                     $descendantsRemoved++;
 622                 }
 623 
 624             // delete the record
 625             $record->doDeleteFromLive();
 626         }
 627 
 628         Versioned::reading_stage('Stage');
 629 
 630         if(isset($descendantsRemoved)) {
 631             $descRemoved = " and $descendantsRemoved descendants";
 632             $descRemoved = sprintf(' '._t('CMSMain.DESCREMOVED', 'and %s descendants'), $descendantsRemoved);
 633         } else {
 634             $descRemoved = '';
 635         }
 636 
 637         FormResponse::add($this->deleteTreeNodeJS($record));
 638         FormResponse::status_message(sprintf(_t('CMSMain.REMOVED', 'Deleted \'%s\'%s from live site'), $record->Title, $descRemoved), 'good');
 639 
 640         return FormResponse::respond();
 641     }
 642 
 643     /**
 644      * Actually perform the publication step
 645      */
 646     public function performPublish($record) {
 647         if($record && !$record->canPublish()) return Security::permissionFailure($this);
 648         
 649         $record->doPublish();
 650     }
 651 
 652     /**
 653      * Reverts a page by publishing it to live.
 654      * Use {@link restorepage()} if you want to restore a page
 655      * which was deleted from draft without publishing.
 656      * 
 657      * @uses SiteTree->doRevertToLive()
 658      */
 659     public function revert($urlParams, $form) {
 660         $id = (int)$_REQUEST['ID'];
 661         $record = Versioned::get_one_by_stage('SiteTree', 'Live', "\"SiteTree_Live\".\"ID\" = '{$id}'");
 662 
 663         // a user can restore a page without publication rights, as it just adds a new draft state
 664         // (this action should just be available when page has been "deleted from draft")
 665         if(isset($record) && $record && !$record->canEdit()) return Security::permissionFailure($this);
 666 
 667         $record->doRevertToLive();
 668 
 669         $title = Convert::raw2js($record->Title);
 670         FormResponse::get_page($id);
 671         FormResponse::add("$('sitetree').setNodeTitle($id, '$title');");
 672         FormResponse::status_message(sprintf(_t('CMSMain.RESTORED',"Restored '%s' successfully",PR_MEDIUM,'Param %s is a title'),$title),'good');
 673 
 674         return FormResponse::respond();
 675     }
 676     
 677     /**
 678      * Delete the current page from draft stage.
 679      * @see deletefromlive()
 680      */
 681     public function delete($data, $form) {
 682         $record = DataObject::get_one(
 683             "SiteTree", 
 684             sprintf("\"SiteTree\".\"ID\" = %d", Convert::raw2sql($data['ID']))
 685         );
 686         if($record && !$record->canDelete()) return Security::permissionFailure();
 687         
 688         // save ID and delete record
 689         $recordID = $record->ID;
 690         $record->delete();
 691         
 692         if(Director::is_ajax()) {
 693             // need a valid ID value even if the record doesn't have one in the database
 694             // (its still present in the live tables)
 695             $liveRecord = Versioned::get_one_by_stage('SiteTree', 'Live', "\"SiteTree_Live\".\"ID\" = $recordID");
 696             // if the page has never been published to live, we need to act the same way as in deletefromlive()
 697             if($liveRecord) {
 698                 // the form is readonly now, so we need to refresh the representation
 699                 FormResponse::get_page($recordID);
 700                 return $this->tellBrowserAboutPublicationChange($liveRecord, sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"),$record->Title));
 701             } else {
 702                 FormResponse::add($this->deleteTreeNodeJS($record));
 703                 FormResponse::status_message(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"),$record->Title), 'good');
 704                 return FormResponse::respond();
 705             }           
 706         } else {
 707             Director::redirectBack();
 708         }
 709     }
 710     
 711     function SideReports() {
 712         return SS_Report::get_reports('SideReport');
 713     }
 714 
 715     /*
 716      * Return a dropdown for selecting reports
 717      */
 718     function ReportSelector() {
 719         // $options[""] = _t('CMSMain.CHOOSEREPORT',"(Choose a report)");
 720         foreach($this->SideReports() as $report) {
 721             if($report->canView()) {
 722                 $options[$report->group()][$report->sort()][$report->ID()] = $report->title();
 723             }
 724         }
 725         
 726         $finalOptions = array();
 727         foreach($options as $group => $weights) {
 728             ksort($weights);
 729             foreach($weights as $weight => $reports) {
 730                 foreach($reports as $class => $report) {
 731                     $finalOptions[$group][$class] = $report;
 732                 }
 733             }
 734         }
 735         
 736         return new GroupedDropdownField("ReportSelector", _t('CMSMain.REPORT', 'Report'),$finalOptions);
 737     }
 738     
 739     /**
 740      * Generate the parameter HTML for SideReports that have params
 741      *
 742      * @return LiteralField
 743      */
 744     function ReportFormParameters() {
 745         $forms = array();
 746         foreach($this->SideReports() as $report) {
 747             if ($report->canView()) {
 748                 if ($fieldset = $report->parameterFields()) {
 749                     $formHtml = '';
 750                     foreach($fieldset as $field) {
 751                         $formHtml .= $field->FieldHolder();
 752                     }
 753                     $forms[$report->ID()] = $formHtml;
 754                 }
 755             }
 756         }
 757         $pageHtml = '';
 758         foreach($forms as $class => $html) {
 759             $pageHtml .= "<div id=\"SideReportForm_$class\" style=\"display:none\">$html</div>\n\n";
 760         } 
 761         return new LiteralField("ReportFormParameters", '<div id="SideReportForms" style="display:none">'.$pageHtml.'</div>');
 762     }
 763     
 764     /**
 765      * Get the content for a side report
 766      */
 767     function sidereport() {
 768         $reportClass = $this->urlParams['ID'];
 769         $reports = $this->SideReports();
 770         if(isset($reports[$reportClass])) {
 771             $report = $reports[$reportClass];
 772             if($report) {
 773                 $view = new SideReportView($this, $report);
 774                 $view->setParameters($this->request->requestVars());
 775                 return $view->forTemplate();
 776             } else {
 777                 return false;
 778             }
 779         }
 780     }
 781     
 782     
 783     /**
 784      * Get the versions of the current page
 785      */
 786     function versions() {
 787         $pageID = $this->urlParams['ID'];
 788         $page = $this->getRecord($pageID);
 789         if($page) {
 790             $versions = $page->allVersions($_REQUEST['unpublished'] ? "" : "\"SiteTree\".\"WasPublished\" = 1");
 791             return array(
 792                 'Versions' => $versions,
 793             );
 794         } else {
 795             return sprintf(_t('CMSMain.VERSIONSNOPAGE',"Can't find page #%d",PR_LOW),$pageID);
 796         }
 797     }
 798 
 799     /**
 800      * Roll a page back to a previous version
 801      */
 802     function rollback() {
 803         if(isset($_REQUEST['Version']) && (bool)$_REQUEST['Version']) {
 804             $this->extend('onBeforeRollback', $_REQUEST['ID']);
 805             $record = $this->performRollback($_REQUEST['ID'], $_REQUEST['Version']);
 806             echo sprintf(_t('CMSMain.ROLLEDBACKVERSION',"Rolled back to version #%d.  New version number is #%d"),$_REQUEST['Version'],$record->Version);
 807         } else {
 808             $record = $this->performRollback($_REQUEST['ID'], "Live");
 809             echo sprintf(_t('CMSMain.ROLLEDBACKPUB',"Rolled back to published version. New version number is #%d"),$record->Version);
 810         }
 811     }
 812 
 813     function unpublish() {
 814         $SQL_id = Convert::raw2sql($_REQUEST['ID']);
 815 
 816         $page = DataObject::get_by_id("SiteTree", $SQL_id);
 817         
 818         if($page && !$page->canDeleteFromLive()) return Security::permissionFailure($this);
 819         
 820         $page->doUnpublish();
 821         
 822         return $this->tellBrowserAboutPublicationChange($page, sprintf(_t('CMSMain.REMOVEDPAGE',"Removed '%s' from the published site"),$page->Title));
 823     }
 824     
 825     /**
 826      * Return a few pieces of information about a change to a page
 827      *  - Send the new status message
 828      *  - Update the action buttons
 829      *  - Update the treenote
 830      *  - Send a status message
 831      */
 832     function tellBrowserAboutPublicationChange($page, $statusMessage) {
 833         $JS_title = Convert::raw2js($page->TreeTitle());
 834 
 835         $JS_stageURL = $page->IsDeletedFromStage ? '' : Convert::raw2js($page->AbsoluteLink());
 836         $liveRecord = Versioned::get_one_by_stage('SiteTree', 'Live', "\"SiteTree\".\"ID\" = $page->ID");
 837 
 838         $JS_liveURL = $liveRecord ? Convert::raw2js($liveRecord->AbsoluteLink()) : '';
 839 
 840         FormResponse::add($this->getActionUpdateJS($page));
 841         FormResponse::update_status($page->Status);
 842         
 843         if($JS_stageURL || $JS_liveURL) {
 844             FormResponse::add("\$('sitetree').setNodeTitle($page->ID, '$JS_title');");
 845         } else {
 846             FormResponse::add("var node = $('sitetree').getTreeNodeByIdx('$page->ID');");
 847             FormResponse::add("if(node && node.parentTreeNode) node.parentTreeNode.removeTreeNode(node);");
 848             FormResponse::add("$('Form_EditForm').reloadIfSetTo($page->ID);");
 849         }
 850         
 851         if($statusMessage) FormResponse::status_message($statusMessage, 'good');
 852         FormResponse::add("$('Form_EditForm').elements.StageURLSegment.value = '$JS_stageURL';");
 853         FormResponse::add("$('Form_EditForm').elements.LiveURLSegment.value = '$JS_liveURL';");
 854         FormResponse::add("$('Form_EditForm').notify('PagePublished', $('Form_EditForm').elements.ID.value);");
 855 
 856         return FormResponse::respond();
 857     }
 858 
 859     function performRollback($id, $version) {
 860         $record = DataObject::get_by_id($this->stat('tree_class'), $id);
 861         if($record && !$record->canEdit()) return Security::permissionFailure($this);
 862         
 863         $record->doRollbackTo($version);
 864         return $record;
 865     }
 866 
 867     function getversion() {
 868         $id = $this->urlParams['ID'];
 869         $version = str_replace('&ajax=1','',$this->urlParams['OtherID']);
 870         $record = Versioned::get_version("SiteTree", $id, $version);
 871         
 872         if($record) {
 873             if($record && !$record->canView()) return Security::permissionFailure($this);
 874             $fields = $record->getCMSFields($this);
 875             $fields->removeByName("Status");
 876 
 877             $fields->push(new HiddenField("ID"));
 878             $fields->push(new HiddenField("Version"));
 879             
 880             $versionAuthor = DataObject::get_by_id('Member', $record->AuthorID);
 881             if(!$versionAuthor) $versionAuthor = new ArrayData(array('Title' => 'Unknown author'));
 882             $fields->insertBefore(
 883                 new LiteralField(
 884                     'YouAreViewingHeader', 
 885                     '<p class="message notice">' .
 886                     sprintf(
 887                         _t(
 888                             'CMSMain.VIEWING',
 889                             "You are viewing version #%s, created %s by %s",
 890                             PR_MEDIUM,
 891                             'Version number is a linked string, created is a relative time (e.g. 2 days ago), by a specific author'
 892                         ),
 893                         "<a href=\"admin/getversion/$record->ID/$version\" title=\"" . ($versionAuthor ? $versionAuthor->Title : '') . "\">$version</a>", 
 894                         $record->obj('LastEdited')->Ago(),
 895                         ($versionAuthor ? $versionAuthor->Title : '')
 896                     ) .
 897                     '</p>'
 898                 ),
 899                 'Root'
 900             );
 901 
 902             $actions = new FieldSet(
 903                 new FormAction("email", _t('CMSMain.EMAIL',"Email")),
 904                 new FormAction("rollback", _t('CMSMain.ROLLBACK',"Roll back to this version"))
 905             );
 906 
 907             // encode the message to appear in the body of the email
 908             $archiveURL = Director::absoluteBaseURL() . $record->URLSegment . '?archiveDate=' . $record->obj('LastEdited')->URLDatetime();
 909             
 910             // Ensure that source file comments are disabled
 911             SSViewer::set_source_file_comments(false);
 912             
 913             $archiveEmailMessage = urlencode( $this->customise( array( 'ArchiveDate' => $record->obj('LastEdited'), 'ArchiveURL' => $archiveURL ) )->renderWith( 'ViewArchivedEmail' ) );
 914             $archiveEmailMessage = preg_replace( '/\+/', '%20', $archiveEmailMessage );
 915 
 916             $fields->push( new HiddenField( 'ArchiveEmailMessage', '', $archiveEmailMessage ) );
 917             $fields->push( new HiddenField( 'ArchiveEmailSubject', '', preg_replace( '/\+/', '%20', urlencode( 'Archived version of ' . $record->Title ) ) ) );
 918             $fields->push( new HiddenField( 'ArchiveURL', '', $archiveURL ) );
 919 
 920             $form = new Form($this, "EditForm", $fields, $actions);
 921             $form->loadDataFrom($record);
 922             $form->loadDataFrom(array(
 923                 "ID" => $id,
 924                 "Version" => $version,
 925             ));
 926             
 927             // historical version shouldn't be editable
 928             $readonlyFields = $form->Fields()->makeReadonly();
 929             $form->setFields($readonlyFields);
 930 
 931             $templateData = $this->customise(array(
 932                 "EditForm" => $form
 933             ));
 934 
 935             SSViewer::setOption('rewriteHashlinks', false);
 936             
 937             if(Director::is_ajax()) {
 938                 $result = $templateData->renderWith($this->class . '_right');
 939                 $parts = split('</?form[^>]*>', $result);
 940                 $content = $parts[sizeof($parts)-2];
 941                 if($this->ShowSwitchView()) {
 942                     $content .= '<div id="AjaxSwitchView">' . $this->SwitchView($record) . '</div>';
 943                 }
 944                 return $content;
 945             } else {
 946                 return $templateData->renderWith('LeftAndMain');
 947             }
 948         }
 949     }
 950 
 951     function compareversions() {
 952         $id = (int)$this->urlParams['ID'];
 953         $version1 = (int)$_REQUEST['From'];
 954         $version2 = (int)$_REQUEST['To'];
 955 
 956         if( $version1 > $version2 ) {
 957             $toVersion = $version1;
 958             $fromVersion = $version2;
 959         } else {
 960             $toVersion = $version2;
 961             $fromVersion = $version1;
 962         }
 963 
 964         $page = DataObject::get_by_id("SiteTree", $id);
 965         if($page && !$page->canView()) return Security::permissionFailure($this);
 966         
 967         $record = $page->compareVersions($fromVersion, $toVersion);
 968         
 969         $fromVersionRecord = Versioned::get_version('SiteTree', $id, $fromVersion);
 970         $toVersionRecord = Versioned::get_version('SiteTree', $id, $toVersion);
 971         if(!$fromVersionRecord) user_error("Can't find version $fromVersion of page $id", E_USER_ERROR);
 972         if(!$toVersionRecord) user_error("Can't find version $toVersion of page $id", E_USER_ERROR);
 973         
 974         if($record) {
 975             $fromDateNice = $fromVersionRecord->obj('LastEdited')->Ago();
 976             $toDateNice = $toVersionRecord->obj('LastEdited')->Ago();
 977             $fromAuthor = DataObject::get_by_id('Member', $fromVersionRecord->AuthorID);
 978             if(!$fromAuthor) $fromAuthor = new ArrayData(array('Title' => 'Unknown author'));
 979             $toAuthor = DataObject::get_by_id('Member', $toVersionRecord->AuthorID);
 980             if(!$toAuthor) $toAuthor = new ArrayData(array('Title' => 'Unknown author'));
 981 
 982             $fields = $record->getCMSFields($this);
 983             $fields->push(new HiddenField("ID"));
 984             $fields->push(new HiddenField("Version"));
 985             $fields->insertBefore(
 986                 new LiteralField(
 987                     'YouAreComparingHeader',
 988                     '<p class="message notice">' . 
 989                     sprintf(
 990                         _t('CMSMain.COMPARINGV',"Comparing versions %s and %s"),
 991                         "<a href=\"admin/getversion/$id/$fromVersionRecord->Version\" title=\"$fromAuthor->Title\">$fromVersionRecord->Version</a> <small>($fromDateNice)</small>",
 992                         "<a href=\"admin/getversion/$id/$toVersionRecord->Version\" title=\"$toAuthor->Title\">$toVersionRecord->Version</a> <small>($toDateNice)</small>"
 993                     ) .
 994                     '</p>'
 995                 ), 
 996                 "Root"
 997             );
 998 
 999             $actions = new FieldSet();
1000 
1001             $form = new Form($this, "EditForm", $fields, $actions);
1002             $form->loadDataFrom($record);
1003             $form->loadDataFrom(array(
1004                 "ID" => $id,
1005                 "Version" => $fromVersion,
1006             ));
1007             
1008             // comparison views shouldn't be editable
1009             $readonlyFields = $form->Fields()->makeReadonly();
1010             $form->setFields($readonlyFields);
1011             
1012             foreach($form->Fields()->dataFields() as $field) {
1013                 $field->dontEscape = true;
1014             }
1015 
1016             return $this->sendFormToBrowser(array(
1017                 "EditForm" => $form
1018             ));
1019         }
1020     }
1021 
1022     function sendFormToBrowser($templateData) {
1023         if(Director::is_ajax()) {
1024             SSViewer::setOption('rewriteHashlinks', false);
1025             $result = $this->customise($templateData)->renderWith($this->class . '_right');
1026             $parts = split('</?form[^>]*>', $result);
1027             return $parts[sizeof($parts)-2];
1028         } else {
1029             return array(
1030                 "Right" => $this->customise($templateData)->renderWith($this->class . '_right'),
1031             );
1032         }
1033     }
1034 
1035     function dialog() {
1036         Requirements::clear();
1037 
1038         $buttons = new DataObjectSet;
1039         if($_REQUEST['Buttons']) foreach($_REQUEST['Buttons'] as $button) {
1040             list($name, $title) = explode(',',$button,2);
1041             $buttons->push(new ArrayData(array(
1042                 "Name" => $name,
1043                 "Title" => $title,
1044             )));
1045         }
1046 
1047         return array(
1048             "Message" => htmlentities($_REQUEST['Message']),
1049             "Buttons" => $buttons,
1050             "Modal" => $_REQUEST['Modal'] ? true : false,
1051         );
1052     }
1053 
1054     function canceldraftchangesdialog() {
1055         Requirements::clear();
1056         Requirements::css(CMS_DIR . 'css/dialog.css');
1057         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
1058         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
1059         Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototype_improvements.js');
1060         Requirements::javascript(CMS_DIR . '/javascript/dialog.js');
1061 
1062         $message = _t('CMSMain.COPYPUBTOSTAGE',"Do you really want to copy the published content to the stage site?");
1063         $buttons = "<button name=\"OK\">" . _t('CMSMain.OK','OK') ."</button><button name=\"Cancel\">" . _t('CMSMain.CANCEL',"Cancel") . "</button>";
1064 
1065         return $this->customise( array(
1066             'Message' => $message,
1067             'Buttons' => $buttons,
1068             'DialogType' => 'alert'
1069         ))->renderWith('Dialog');
1070     }
1071     
1072     /**
1073      * Batch Actions Handler
1074      */
1075     function batchactions() {
1076         return new CMSBatchActionHandler($this, 'batchactions');
1077     }
1078     
1079     /**
1080      * @return Form
1081      */
1082     public function PublishItemsForm() {
1083         $form = new Form(
1084             $this,
1085             'PublishItemsForm',
1086             new FieldSet(
1087                 new HiddenField('csvIDs'),
1088                 new CheckboxField('ShowDrafts', _t('CMSMain_left.ss.SHOWONLYCHANGED','Show only changed pages'))
1089             ),
1090             new FieldSet(
1091                 new FormAction('publishitems', _t('CMSMain_left.ss.PUBLISHCONFIRM','Publish the selected pages'))
1092             )
1093         );
1094         $form->addExtraClass('actionparams');
1095         return $form;
1096     }
1097 
1098     function BatchActionParameters() {
1099         $batchActions = CMSBatchActionHandler::$batch_actions;
1100 
1101         $forms = array();
1102         foreach($batchActions as $urlSegment => $batchAction) {
1103             $SNG_action = singleton($batchAction);
1104             if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) {
1105                 $formHtml = '';
1106                 foreach($fieldset as $field) {
1107                     $formHtml .= $field->Field();
1108                 }
1109                 $forms[$urlSegment] = $formHtml;
1110             }
1111         }
1112         $pageHtml = '';
1113         foreach($forms as $urlSegment => $html) {
1114             $pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n";
1115         } 
1116         return new LiteralField("BatchActionParameters", '<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
1117     }
1118     /**
1119      * Returns a list of batch actions
1120      */
1121     function BatchActionList() {
1122         return $this->batchactions()->batchActionList();
1123     }
1124     
1125     /**
1126      * @return Form
1127      */
1128     function DeleteItemsForm() {
1129         $form = new Form(
1130             $this,
1131             'DeleteItemsForm',
1132             new FieldSet(
1133                 new LiteralField('SelectedPagesNote',
1134                     sprintf('<p>%s</p>', _t('CMSMain_left.ss.SELECTPAGESACTIONS','Select the pages that you want to change &amp; then click an action:'))
1135                 ),
1136                 new HiddenField('csvIDs')
1137             ),
1138             new FieldSet(
1139                 new FormAction('deleteitems', _t('CMSMain_left.ss.DELETECONFIRM','Delete the selected pages'))
1140             )
1141         );
1142         $form->addExtraClass('actionparams');
1143         return $form;
1144     }
1145 
1146     function buildbrokenlinks() {
1147         if($this->urlParams['ID']) {
1148             $newPageSet[] = DataObject::get_by_id("Page", $this->urlParams['ID']);
1149         } else {
1150             $pages = DataObject::get("Page");
1151             foreach($pages as $page) $newPageSet[] = $page;
1152             $pages = null;
1153         }
1154 
1155         $content = new HtmlEditorField('Content');
1156         $download = new HtmlEditorField('Download');
1157 
1158         foreach($newPageSet as $i => $page) {
1159             $page->HasBrokenLink = 0;
1160             $page->HasBrokenFile = 0;
1161 
1162             $lastUsage = (memory_get_usage() - $lastPoint);
1163             $lastPoint = memory_get_usage();
1164             $content->setValue($page->Content);
1165             $content->saveInto($page);
1166 
1167             $download->setValue($page->Download);
1168             $download->saveInto($page);
1169 
1170             echo "<li>$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)";
1171 
1172             $page->writeWithoutVersion();
1173             $page->destroy();
1174             $newPageSet[$i] = null;
1175         }
1176     }
1177 
1178     function AddPageOptionsForm() {
1179         $pageTypes = array();
1180 
1181         foreach( $this->PageTypes() as $arrayData ) {
1182             $pageTypes[$arrayData->getField('ClassName')] = $arrayData->getField('AddAction');
1183         }
1184         
1185         $fields = new FieldSet(
1186             new HiddenField("ParentID"),
1187             new HiddenField("Locale", 'Locale', Translatable::get_current_locale()),
1188             new DropdownField("PageType", "", $pageTypes, 'Page')
1189         );
1190         
1191         $this->extend('updatePageOptions', $fields);
1192         
1193         $actions = new FieldSet(
1194             new FormAction("addpage", _t('CMSMain.GO',"Go"))
1195         );
1196 
1197         return new Form($this, "AddPageOptionsForm", $fields, $actions);
1198     }
1199 
1200     /**
1201      * Helper function to get page count
1202      */
1203     function getpagecount() {
1204         ini_set('max_execution_time', 0);
1205         $excludePages = split(" *, *", $_GET['exclude']);
1206 
1207         $pages = DataObject::get("SiteTree", "\"ParentID\" = 0");
1208         foreach($pages as $page) $pageArr[] = $page;
1209 
1210         while(list($i,$page) = each($pageArr)) {
1211             if(!in_array($page->URLSegment, $excludePages)) {
1212                 if($children = $page->AllChildren()) {
1213                     foreach($children as $child) $pageArr[] = $child;
1214                 }
1215 
1216 
1217                 if(!$_GET['onlywithcontent'] || strlen(Convert::xml2raw($page->Content)) > 100) {
1218                     echo "<li>" . $page->Breadcrumbs(null, true) . "</li>";
1219                     $count++;
1220                 } else {
1221                     echo "<li style=\"color: #777\">" . $page->Breadcrumbs(null, true) . " - " . _t('CMSMain.NOCONTENT',"no content") . "</li>";
1222                 }
1223 
1224             }
1225         }
1226 
1227         echo '<p>' . _t('CMSMain.TOTALPAGES',"Total pages: ") . "$count</p>";
1228     }
1229 
1230     function publishall() {
1231         ini_set("memory_limit", -1);
1232         ini_set('max_execution_time', 0);
1233         
1234         $response = "";
1235 
1236         if(isset($this->requestParams['confirm'])) {
1237             $start = 0;
1238             $pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1239             $count = 0;
1240             if($pages){
1241                 while(true) {
1242                     foreach($pages as $page) {
1243                         if($page && !$page->canPublish()) return Security::permissionFailure($this);
1244                         
1245                         $page->doPublish();
1246                         $page->destroy();
1247                         unset($page);
1248                         $count++;
1249                         $response .= "<li>$count</li>";
1250                     }
1251                     if($pages->Count() > 29) {
1252                         $start += 30;
1253                         $pages = DataObject::get("SiteTree", "", "", "", "$start,30");
1254                     } else {
1255                         break;
1256                     }
1257                 }
1258             }
1259             $response .= sprintf(_t('CMSMain.PUBPAGES',"Done: Published %d pages"), $count);
1260 
1261         } else {
1262             $response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
1263                 <p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish".  It\'s
1264                 intended to be used after there have been massive edits of the content, such as when the site was
1265                 first built.') . '</p>
1266                 <form method="post" action="publishall">
1267                     <input type="submit" name="confirm" value="'
1268                     . _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",PR_LOW,'Confirmation button') .'" />
1269                 </form>';
1270         }
1271         
1272         return $response;
1273     }
1274     
1275     /**
1276      * Restore a completely deleted page from the SiteTree_versions table.
1277      */
1278     function restore() {
1279         if(($id = $_REQUEST['ID']) && is_numeric($id)) {
1280             $restoredPage = Versioned::get_latest_version("SiteTree", $id);
1281             if($restoredPage) {
1282                 $restoredPage = $restoredPage->doRestoreToStage();
1283 
1284                 FormResponse::get_page($id);
1285                 $title = Convert::raw2js($restoredPage->TreeTitle());
1286                 FormResponse::add("$('sitetree').setNodeTitle($id, '$title');");
1287                 FormResponse::status_message(sprintf(_t('CMSMain.RESTORED',"Restored '%s' successfully",PR_MEDIUM,'Param %s is a title'),$title),'good');
1288                 return FormResponse::respond();
1289 
1290             } else {
1291                 return new SS_HTTPResponse("SiteTree #$id not found", 400);
1292             }
1293         } else {
1294             return new SS_HTTPResponse("Please pass an ID in the form content", 400);
1295         }
1296     }
1297 
1298     function duplicate() {
1299         if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1300             $page = DataObject::get_by_id("SiteTree", $id);
1301             if($page && (!$page->canEdit() || !$page->canCreate())) {
1302                 return Security::permissionFailure($this);
1303             }
1304 
1305             $newPage = $page->duplicate();
1306             
1307             // ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
1308             if($_GET['parentID'] && is_numeric($_GET['parentID'])) {
1309                 $newPage->ParentID = $_GET['parentID'];
1310                 $newPage->write();
1311             }
1312 
1313             return $this->returnItemToUser($newPage);
1314         } else {
1315             user_error("CMSMain::duplicate() Bad ID: '$id'", E_USER_WARNING);
1316         }
1317     }
1318 
1319     function duplicatewithchildren() {
1320         if(($id = $this->urlParams['ID']) && is_numeric($id)) {
1321             $page = DataObject::get_by_id("SiteTree", $id);
1322             if($page && (!$page->canEdit() || !$page->canCreate())) {
1323                 return Security::permissionFailure($this);
1324             }
1325 
1326             $newPage = $page->duplicateWithChildren();
1327 
1328             return $this->returnItemToUser($newPage);
1329         } else {
1330             user_error("CMSMain::duplicate() Bad ID: '$id'", E_USER_WARNING);
1331         }
1332     }
1333     
1334 
1335     
1336     /**
1337      * Create a new translation from an existing item, switch to this language and reload the tree.
1338      */
1339     function createtranslation () {
1340         $langCode = Convert::raw2sql($_REQUEST['newlang']);
1341         $originalLangID = (int)$_REQUEST['ID'];
1342 
1343         $record = $this->getRecord($originalLangID);
1344         
1345         $this->Locale = $langCode;
1346         Translatable::set_current_locale($langCode);
1347         
1348         // Create a new record in the database - this is different
1349         // to the usual "create page" pattern of storing the record
1350         // in-memory until a "save" is performed by the user, mainly
1351         // to simplify things a bit.
1352         // @todo Allow in-memory creation of translations that don't persist in the database before the user requests it
1353         $translatedRecord = $record->createTranslation($langCode);
1354 
1355         $url = sprintf(
1356             "%s/%d/?locale=%s", 
1357             $this->Link('show'),
1358             $translatedRecord->ID,
1359             $langCode
1360         );
1361         FormResponse::add(sprintf('window.location.href = "%s";', $url));
1362         return FormResponse::respond();
1363     }
1364 
1365     /**
1366      * Provide the permission codes used by LeftAndMain.
1367      * Can't put it on LeftAndMain since that's an abstract base class.
1368      */
1369     function providePermissions() {
1370         $classes = ClassInfo::subclassesFor('LeftAndMain');
1371 
1372         foreach($classes as $i => $class) {
1373             $title = _t("{$class}.MENUTITLE", LeftAndMain::menu_title_for_class($class));
1374             $perms["CMS_ACCESS_" . $class] = array(
1375                 'name' => sprintf(_t(
1376                     'CMSMain.ACCESS', 
1377                     "Access to '%s' section",
1378                     PR_MEDIUM,
1379                     "Item in permission selection identifying the admin section. Example: Access to 'Files & Images'"
1380                 ), $title, null),
1381                 'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
1382             );
1383         }
1384         $perms["CMS_ACCESS_LeftAndMain"] = array(
1385             'name' => _t('CMSMain.ACCESSALLINTERFACES', 'Access to all CMS sections'),
1386             'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
1387             'help' => _t('CMSMain.ACCESSALLINTERFACESHELP', 'Overrules more specific access settings.'),
1388             'sort' => -100
1389         );
1390         $perms['CMS_ACCESS_CMSMain']['help'] = _t(
1391             'CMSMain.ACCESS_HELP',
1392             'Allow viewing of the section containing page tree and content. View and edit permissions can be handled through page specific dropdowns, as well as the separate "Content permissions".'
1393         );
1394         $perms['CMS_ACCESS_SecurityAdmin']['help'] = _t(
1395             'SecurityAdmin.ACCESS_HELP',
1396             'Allow viewing, adding and editing users, as well as assigning permissions and roles to them.'
1397         );
1398 
1399         if (isset($perms['CMS_ACCESS_ModelAdmin'])) unset($perms['CMS_ACCESS_ModelAdmin']);
1400 
1401         return $perms;
1402     }
1403     
1404     /**
1405      * Returns all languages with languages already used appearing first.
1406      * Called by the SSViewer when rendering the template.
1407      */
1408     function LangSelector() {
1409         $member = Member::currentUser(); 
1410         $dropdown = new LanguageDropdownField(
1411             'LangSelector', 
1412             'Language', 
1413             array(),
1414             'SiteTree', 
1415             'Locale-English',
1416             singleton('SiteTree')
1417         );
1418         $dropdown->setValue(Translatable::get_current_locale());
1419         return $dropdown;
1420     }
1421 
1422     /**
1423      * Determine if there are more than one languages in our site tree.
1424      * 
1425      * @return boolean
1426      */
1427     function MultipleLanguages() {
1428         $langs = Translatable::get_existing_content_languages('SiteTree');
1429 
1430         return (count($langs) > 1);
1431     }
1432     
1433     /**
1434      * @return boolean
1435      */
1436     function IsTranslatableEnabled() {
1437         return Object::has_extension('SiteTree', 'Translatable');
1438     }
1439 }
1440 
1441 /**
1442  * @package cms
1443  * @subpackage content
1444  */
1445 class CMSMainMarkingFilter {
1446     
1447     function __construct() {
1448         $this->ids = array();
1449         $this->expanded = array();
1450         
1451         $where = array();
1452         
1453         // Match against URLSegment, Title, MenuTitle & Content
1454         if (isset($_REQUEST['SiteTreeSearchTerm'])) {
1455             $term = Convert::raw2sql($_REQUEST['SiteTreeSearchTerm']);
1456             $where[] = "\"URLSegment\" LIKE '%$term%' OR \"Title\" LIKE '%$term%' OR \"MenuTitle\" LIKE '%$term%' OR \"Content\" LIKE '%$term%'";
1457         }
1458         
1459         // Match against date
1460         if (isset($_REQUEST['SiteTreeFilterDate'])) {
1461             $date = $_REQUEST['SiteTreeFilterDate'];
1462             $date = ((int)substr($date,6,4)) . '-' . ((int)substr($date,3,2)) . '-' . ((int)substr($date,0,2));
1463             $where[] = "\"LastEdited\" > '$date'"; 
1464         }
1465         
1466         // Match against exact ClassName
1467         if (isset($_REQUEST['ClassName']) && $_REQUEST['ClassName'] != 'All') {
1468             $klass = Convert::raw2sql($_REQUEST['ClassName']);
1469             $where[] = "\"ClassName\" = '$klass'";
1470         }
1471         
1472         // Partial string match against a variety of fields 
1473         foreach (CMSMain::T_SiteTreeFilterOptions() as $key => $value) {
1474             if (!empty($_REQUEST[$key])) {
1475                 $match = Convert::raw2sql($_REQUEST[$key]);
1476                 $where[] = "\"$key\" LIKE '%$match%'";
1477             }
1478         }
1479         
1480         $where = empty($where) ? '' : 'WHERE (' . implode(') AND (',$where) . ')';
1481         
1482         $parents = array();
1483         
1484         /* Do the actual search */
1485         $res = DB::query('SELECT "ParentID", "ID" FROM "SiteTree" '.$where);
1486         if (!$res) return;
1487         
1488         /* And keep a record of parents we don't need to get parents of themselves, as well as IDs to mark */
1489         foreach($res as $row) {
1490             if ($row['ParentID']) $parents[$row['ParentID']] = true;
1491             $this->ids[$row['ID']] = true;
1492         }
1493         
1494         /* We need to recurse up the tree, finding ParentIDs for each ID until we run out of parents */
1495         while (!empty($parents)) {
1496             $res = DB::query('SELECT "ParentID", "ID" FROM "SiteTree" WHERE "ID" in ('.implode(',',array_keys($parents)).')');
1497             $parents = array();
1498 
1499             foreach($res as $row) {
1500                 if ($row['ParentID']) $parents[$row['ParentID']] = true;
1501                 $this->ids[$row['ID']] = true;
1502                 $this->expanded[$row['ID']] = true;
1503             }
1504         }
1505     }
1506     
1507     function mark($node) {
1508         $id = $node->ID;
1509         if(array_key_exists((int) $id, $this->expanded)) $node->markOpened();
1510         return array_key_exists((int) $id, $this->ids) ? $this->ids[$id] : false;
1511     }
1512 }
1513 
1514 ?>
1515 
[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.1 API Docs API documentation generated by ApiGen 2.8.0