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

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