Version: current

       1 <?php
       2 /**
       3  * LeftAndMain is the parent class of all the two-pane views in the CMS.
       4  * If you are wanting to add more areas to the CMS, you can do it by subclassing LeftAndMain.
       5  *
       6  * This is essentially an abstract class which should be subclassed.
       7  * See {@link CMSMain} for a good example.
       8  *
       9  * @package cms
      10  * @subpackage core
      11  */
      12 class LeftAndMain extends Controller {
      13
      14     /**
      15      * The 'base' url for CMS administration areas.
      16      * Note that if this is changed, many javascript
      17      * behaviours need to be updated with the correct url
      18      *
      19      * @var string $url_base
      20      */
      21     static $url_base = "admin";
      22
      23     static $url_segment;
      24
      25     static $url_rule = '/$Action/$ID/$OtherID';
      26
      27     static $menu_title;
      28
      29     static $menu_priority = 0;
      30
      31     static $url_priority = 50;
      32
      33     static $tree_class = null;
      34
      35     static $ForceReload;
      36
      37     static $allowed_actions = array(
      38         'index',
      39         'ajaxupdateparent',
      40         'ajaxupdatesort',
      41         'callPageMethod',
      42         'deleteitems',
      43         'getitem',
      44         'getsubtree',
      45         'myprofile',
      46         'printable',
      47         'save',
      48         'show',
      49         'Member_ProfileForm',
      50         'EditorToolbar',
      51         'EditForm',
      52     );
      53
      54     /**
      55      * Register additional requirements through the {@link Requirements class}.
      56      * Used mainly to work around the missing "lazy loading" functionality
      57      * for getting css/javascript required after an ajax-call (e.g. loading the editform).
      58      *
      59      * @var array $extra_requirements
      60      */
      61     protected static $extra_requirements = array(
      62         'javascript' => array(),
      63         'css' => array(),
      64         'themedcss' => array(),
      65     );
      66
      67     /**
      68      * @param Member $member
      69      * @return boolean
      70      */
      71     function canView($member = null) {
      72         if(!$member && $member !== FALSE) {
      73             $member = Member::currentUser();
      74         }
      75
      76         // cms menus only for logged-in members
      77         if(!$member) return false;
      78
      79         // alternative decorated checks
      80         if($this->hasMethod('alternateAccessCheck')) {
      81             $alternateAllowed = $this->alternateAccessCheck();
      82             if($alternateAllowed === FALSE) return false;
      83         }
      84
      85         // Default security check for LeftAndMain sub-class permissions
      86         if(!Permission::checkMember($member, "CMS_ACCESS_$this->class") &&
      87            !Permission::checkMember($member, "CMS_ACCESS_LeftAndMain")) {
      88             return false;
      89         }
      90
      91         return true;
      92     }
      93
      94     /**
      95      * @uses LeftAndMainDecorator->init()
      96      * @uses LeftAndMainDecorator->accessedCMS()
      97      * @uses CMSMenu
      98      */
      99     function init() {
     100         parent::init();
     101
     102         // set language
     103         $member = Member::currentUser();
     104         if(!empty($member->Locale)) {
     105             i18n::set_locale($member->Locale);
     106         }
     107
     108
     109         // can't be done in cms/_config.php as locale is not set yet
     110         CMSMenu::add_link(
     111             'Help',
     112             _t('LeftAndMain.HELP', 'Help', PR_HIGH, 'Menu title'),
     113             'http://support.mediaweb.ru/faq31/',
     114             -100
     115         );
     116
     117
     118         // set reading lang
     119         if(SS_Object::has_extension('SiteTree', 'Translatable') && !Director::is_ajax()) {
     120             Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree')));
     121         }
     122
     123         // Allow customisation of the access check by a decorator
     124         // Also all the canView() check to execute Director::redirect()
     125         if(!$this->canView() && !$this->response->isFinished()) {
     126             // When access /admin/, we should try a redirect to another part of the admin rather than be locked out
     127             $menu = $this->MainMenu();
     128             foreach($menu as $candidate) {
     129                 if(
     130                     $candidate->Link &&
     131                     $candidate->Link != $this->Link()
     132                     && $candidate->MenuItem->controller
     133                     && singleton($candidate->MenuItem->controller)->canView()
     134                 ) {
     135                     return Director::redirect($candidate->Link);
     136                 }
     137             }
     138
     139             if(Member::currentUser()) {
     140                 Session::set("BackURL", null);
     141             }
     142
     143             // if no alternate menu items have matched, return a permission error
     144             $messageSet = array(
     145                 'default' => _t('LeftAndMain.PERMDEFAULT',"Please choose an authentication method and enter your credentials to access the CMS."),
     146                 'alreadyLoggedIn' => _t('LeftAndMain.PERMALREADY',"I'm sorry, but you can't access that part of the CMS.  If you want to log in as someone else, do so below"),
     147                 'logInAgain' => _t('LeftAndMain.PERMAGAIN',"You have been logged out of the CMS.  If you would like to log in again, enter a username and password below."),
     148             );
     149
     150             return Security::permissionFailure($this, $messageSet);
     151         }
     152
     153         // Don't continue if there's already been a redirection request.
     154         if(Director::redirected_to()) return;
     155
     156         // Audit logging hook
     157         if(empty($_REQUEST['executeForm']) && !Director::is_ajax()) $this->extend('accessedCMS');
     158
     159         // Set the members html editor config
     160         HtmlEditorConfig::set_active(Member::currentUser()->getHtmlEditorConfigForCMS());
     161
     162
     163         // Set default values in the config if missing.  These things can't be defined in the config
     164         // file because insufficient information exists when that is being processed
     165         $htmlEditorConfig = HtmlEditorConfig::get_active();
     166         if(!$htmlEditorConfig->getOption('content_css')) {
     167             $cssFiles = 'cms/css/editor.css';
     168
     169             // Use theme from the site config
     170             if(($config = SiteConfig::current_site_config()) && $config->Theme) {
     171                 $theme = $config->Theme;
     172             } elseif(SSViewer::current_theme()) {
     173                 $theme = SSViewer::current_theme();
     174             } else {
     175                 $theme = false;
     176             }
     177             if ($theme && file_exists(THEMES_PATH . "/{$theme}/css/editor.css")) {
     178                 $cssFiles .= ', ' . THEMES_DIR . "/{$theme}/css/editor.css?m=" . filemtime(THEMES_PATH . "/{$theme}/css/editor.css");
     179             }
     180             else if(project()) {
     181                 $cssFiles .= ', ' . project() . '/css/editor.css?m=' . filemtime(Director::baseFolder() . '/' . project() . "/css/editor.css");
     182             }
     183             $htmlEditorConfig->setOption('content_css', $cssFiles);
     184         }
     185
     186
     187         Requirements::css(CMS_DIR . '/css/typography.css');
     188         Requirements::css(CMS_DIR . '/css/layout.css');
     189         Requirements::css(CMS_DIR . '/css/cms_left.css');
     190         Requirements::css(CMS_DIR . '/css/cms_right.css');
     191         Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
     192
     193         if(isset($_REQUEST['debug_firebug'])) {
     194             // Firebug is a useful console for debugging javascript
     195             // Its available as a Firefox extension or a javascript library
     196             // for easy inclusion in other browsers (just append ?debug_firebug=1 to the URL)
     197             Requirements::javascript(THIRDPARTY_DIR . '/firebug-lite/firebug.js');
     198         } else {
     199             // By default, we include fake-objects for all firebug calls
     200             // to avoid javascript errors when referencing console.log() etc in javascript code
     201             Requirements::javascript(THIRDPARTY_DIR . '/firebug-lite/firebugx.js');
     202         }
     203
     204         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
     205         Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
     206         Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
     207         Requirements::javascript(THIRDPARTY_DIR . '/behaviour/behaviour.js');
     208         Requirements::javascript(THIRDPARTY_DIR . '/jquery-livequery/jquery.livequery.js');
     209         Requirements::javascript(SAPPHIRE_DIR . '/javascript/core/jquery.ondemand.js');
     210         Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototype_improvements.js');
     211         Requirements::javascript(SAPPHIRE_DIR . '/javascript/loader.js');
     212         Requirements::javascript(CMS_DIR . '/javascript/hover.js');
     213         Requirements::javascript(SAPPHIRE_DIR . '/javascript/layout_helpers.js');
     214         Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
     215         Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang');
     216
     217         Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
     218         Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/dragdrop.js');
     219         Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/controls.js');
     220
     221         Requirements::css(THIRDPARTY_DIR . '/greybox/greybox.css');
     222         Requirements::javascript(THIRDPARTY_DIR . '/greybox/AmiJS.js');
     223         Requirements::javascript(THIRDPARTY_DIR . '/greybox/greybox.js');
     224
     225         Requirements::javascript(SAPPHIRE_DIR . '/javascript/tree/tree.js');
     226         Requirements::css(THIRDPARTY_DIR . '/tree/tree.css');
     227
     228         Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.js');
     229         Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain_left.js');
     230         Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain_right.js');
     231
     232         Requirements::javascript(CMS_DIR . '/javascript/SideTabs.js');
     233         Requirements::javascript(CMS_DIR . '/javascript/SideReports.js');
     234         Requirements::javascript(CMS_DIR . '/javascript/LangSelector.js');
     235         Requirements::javascript(CMS_DIR . '/javascript/TranslationTab.js');
     236
     237         // navigator
     238         Requirements::css(SAPPHIRE_DIR . '/css/SilverStripeNavigator.css');
     239         Requirements::javascript(SAPPHIRE_DIR . '/javascript/SilverStripeNavigator.js');
     240
     241         //Requirements::themedCSS('typography');
     242
     243         foreach (self::$extra_requirements['javascript'] as $file) {
     244             Requirements::javascript($file[0]);
     245         }
     246
     247         foreach (self::$extra_requirements['css'] as $file) {
     248             Requirements::css($file[0], $file[1]);
     249         }
     250
     251         foreach (self::$extra_requirements['themedcss'] as $file) {
     252             Requirements::themedCSS($file[0], $file[1]);
     253         }
     254
     255         Requirements::customScript('Behaviour.addLoader(hideLoading);');
     256
     257         // Javascript combined files
     258         Requirements::combine_files(
     259             'assets/_tmp/base.js',
     260             array(
     261                 THIRDPARTY_DIR . '/prototype/prototype.js',
     262                 THIRDPARTY_DIR . '/behaviour/behaviour.js',
     263                 SAPPHIRE_DIR . '/javascript/prototype_improvements.js',
     264                 THIRDPARTY_DIR .'/jquery/jquery.js',
     265                 THIRDPARTY_DIR .'/jquery-livequery/jquery.livequery.js',
     266                 THIRDPARTY_DIR . '/jquery-effen/jquery.fn.js',
     267                 SAPPHIRE_DIR . '/javascript/core/jquery.ondemand.js',
     268                 SAPPHIRE_DIR . '/javascript/jquery_improvements.js',
     269                 THIRDPARTY_DIR . '/firebug-lite/firebugx.js',
     270                 SAPPHIRE_DIR . '/javascript/i18n.js',
     271             )
     272         );
     273
     274         Requirements::combine_files(
     275             'assets/_tmp/leftandmain.js',
     276             array(
     277                 SAPPHIRE_DIR . '/javascript/loader.js',
     278                 CMS_DIR . '/javascript/hover.js',
     279                 SAPPHIRE_DIR . '/javascript/layout_helpers.js',
     280                 THIRDPARTY_DIR . '/scriptaculous/effects.js',
     281                 THIRDPARTY_DIR . '/scriptaculous/dragdrop.js',
     282                 THIRDPARTY_DIR . '/scriptaculous/controls.js',
     283                 THIRDPARTY_DIR . '/greybox/AmiJS.js',
     284                 THIRDPARTY_DIR . '/greybox/greybox.js',
     285                 CMS_DIR . '/javascript/LeftAndMain.js',
     286                 CMS_DIR . '/javascript/LeftAndMain_left.js',
     287                 CMS_DIR . '/javascript/LeftAndMain_right.js',
     288                 SAPPHIRE_DIR . '/javascript/tree/tree.js',
     289                 THIRDPARTY_DIR . '/tabstrip/tabstrip.js',
     290                 SAPPHIRE_DIR . '/javascript/TreeSelectorField.js',
     291                 CMS_DIR . '/javascript/ThumbnailStripField.js',
     292             )
     293         );
     294
     295         Requirements::combine_files(
     296             'assets/_tmp/cmsmain.js',
     297             array(
     298                 CMS_DIR . '/javascript/CMSMain.js',
     299                 CMS_DIR . '/javascript/CMSMain_left.js',
     300                 CMS_DIR . '/javascript/CMSMain_right.js',
     301                 CMS_DIR . '/javascript/SideTabs.js',
     302                 CMS_DIR . '/javascript/SideReports.js',
     303                 CMS_DIR . '/javascript/LangSelector.js',
     304                 CMS_DIR . '/javascript/TranslationTab.js',
     305                 THIRDPARTY_DIR . '/calendar/calendar.js',
     306                 THIRDPARTY_DIR . '/calendar/lang/calendar-'.substr(i18n::get_locale(),0,2).'.js',
     307                 THIRDPARTY_DIR . '/calendar/calendar-setup.js',
     308             )
     309         );
     310
     311         $dummy = null;
     312         $this->extend('init', $dummy);
     313
     314         // выключаем html5-формы в админке
     315         if (property_exists('FormField', 'use_html5')) {
     316             //FormField::allow_html5(false); // пробуем, все-таки, использовать html5 в админке
     317         }
     318         FormField::allow_html5(true);
     319
     320         // The user's theme shouldn't affect the CMS, if, for example, they have replaced
     321         // TableListField.ss or Form.ss.
     322         SSViewer::set_theme(null);
     323     }
     324
     325
     326     /**
     327      * If this is set to true, the "switchView" context in the
     328      * template is shown, with links to the staging and publish site.
     329      *
     330      * @return boolean
     331      */
     332     function ShowSwitchView() {
     333         return false;
     334     }
     335
     336     //------------------------------------------------------------------------------------------//
     337     // Main controllers
     338
     339     /**
     340      * You should implement a Link() function in your subclass of LeftAndMain,
     341      * to point to the URL of that particular controller.
     342      *
     343      * @return string
     344      */
     345     public function Link($action = null) {
     346         // Handle missing url_segments
     347         if(!$this->stat('url_segment', true))
     348             self::$url_segment = $this->class;
     349         return Controller::join_links(
     350             $this->stat('url_base', true),
     351             $this->stat('url_segment', true),
     352             '/', // trailing slash needed if $action is null!
     353             "$action"
     354         );
     355     }
     356
     357
     358     /**
     359     * Returns the menu title for the given LeftAndMain subclass.
     360     * Implemented static so that we can get this value without instantiating an object.
     361     * Menu title is *not* internationalised.
     362     */
     363     static function menu_title_for_class($class) {
     364         $title = eval("return $class::\$menu_title;");
     365         if(!$title) $title = preg_replace('/Admin$/', '', $class);
     366         return $title;
     367     }
     368
     369     public function show($request) {
     370         $params = $this->getURLParams();
     371         if($params['ID']) $this->setCurrentPageID($params['ID']);
     372         if(isset($params['OtherID'])) {
     373             Session::set('currentMember', $params['OtherID']);
     374         }
     375
     376         if(Director::is_ajax()) {
     377             SSViewer::setOption('rewriteHashlinks', false);
     378             return $this->EditForm()->formHtmlContent();
     379
     380         } else {
     381             return array();
     382         }
     383     }
     384
     385
     386     public function getitem() {
     387         $this->setCurrentPageID($_REQUEST['ID']);
     388         SSViewer::setOption('rewriteHashlinks', false);
     389
     390         if(isset($_REQUEST['ID']) && is_numeric($_REQUEST['ID'])) {
     391             $record = DataObject::get_by_id($this->stat('tree_class'), $_REQUEST['ID']);
     392             if($record && !$record->canView()) return Security::permissionFailure($this);
     393         }
     394
     395         $form = $this->EditForm();
     396         if ($form) {
     397             $content =  $form->formHtmlContent();
     398             if($this->ShowSwitchView()) {
     399                 $content .= '<div id="AjaxSwitchView">' . $this->SwitchView() . '</div>';
     400             }
     401
     402             return $content;
     403         }
     404         else return "";
     405     }
     406     public function getLastFormIn($html) {
     407         $parts = preg_split('!</?form[^>]*>!', $html);
     408         return $parts[sizeof($parts)-2];
     409     }
     410
     411     //------------------------------------------------------------------------------------------//
     412     // Main UI components
     413
     414     /**
     415      * Returns the main menu of the CMS.  This is also used by init() to work out which sections the user
     416      * has access to.
     417      *
     418      * @return DataObjectSet
     419      */
     420     public function MainMenu() {
     421         // Don't accidentally return a menu if you're not logged in - it's used to determine access.
     422         if(!Member::currentUser()) return new DataObjectSet();
     423
     424         // Encode into DO set
     425         $menu = new DataObjectSet();
     426         $menuItems = CMSMenu::get_viewable_menu_items();
     427         if($menuItems) foreach($menuItems as $code => $menuItem) {
     428             // alternate permission checks (in addition to LeftAndMain->canView())
     429             if(
     430                 isset($menuItem->controller)
     431                 && $this->hasMethod('alternateMenuDisplayCheck')
     432                 && !$this->alternateMenuDisplayCheck($menuItem->controller)
     433             ) {
     434                 continue;
     435             }
     436
     437             $linkingmode = "";
     438
     439             if(strpos($this->Link(), $menuItem->url) !== false) {
     440                 if($this->Link() == $menuItem->url) {
     441                     $linkingmode = "current";
     442
     443                 // default menu is the one with a blank {@link url_segment}
     444                 } else if(singleton($menuItem->controller)->stat('url_segment') == '') {
     445                     if($this->Link() == $this->stat('url_base').'/') $linkingmode = "current";
     446
     447                 } else {
     448                     $linkingmode = "current";
     449                 }
     450             }
     451
     452             // already set in CMSMenu::populate_menu(), but from a static pre-controller
     453             // context, so doesn't respect the current user locale in _t() calls - as a workaround,
     454             // we simply call LeftAndMain::menu_title_for_class() again if we're dealing with a controller
     455             if($menuItem->controller) {
     456                 $defaultTitle = LeftAndMain::menu_title_for_class($menuItem->controller);
     457                 $title = _t("{$menuItem->controller}.MENUTITLE", $defaultTitle);
     458             } else {
     459                 $title = $menuItem->title;
     460             }
     461
     462             $menu->push(new ArrayData(array(
     463                 "MenuItem" => $menuItem,
     464                 "Title" => Convert::raw2xml($title),
     465                 "Code" => $code,
     466                 "Link" => $menuItem->url,
     467                 "LinkingMode" => $linkingmode
     468             )));
     469         }
     470
     471         // if no current item is found, assume that first item is shown
     472         //if(!isset($foundCurrent))
     473         return $menu;
     474     }
     475
     476
     477     public function CMSTopMenu() {
     478         return $this->renderWith(array('CMSTopMenu_alternative','CMSTopMenu'));
     479     }
     480
     481   /**
     482    * Return a list of appropriate templates for this class, with the given suffix
     483    */
     484   protected function getTemplatesWithSuffix($suffix) {
     485     $classes = array_reverse(ClassInfo::ancestry($this->class));
     486     foreach($classes as $class) {
     487       $templates[] = $class . $suffix;
     488       if($class == 'LeftAndMain') break;
     489     }
     490     return $templates;
     491   }
     492
     493     public function Left() {
     494         return $this->renderWith($this->getTemplatesWithSuffix('_left'));
     495     }
     496
     497     public function Right() {
     498         return $this->renderWith($this->getTemplatesWithSuffix('_right'));
     499     }
     500
     501     public function getRecord($id, $className = null) {
     502         if($id && is_numeric($id)) {
     503             if(!$className) $className = $this->stat('tree_class');
     504             return DataObject::get_by_id($className, $id);
     505         }
     506     }
     507
     508     /**
     509      * Get a site tree displaying the nodes under the given objects
     510      * @param $className The class of the root object
     511      * @param $rootID The ID of the root object.  If this is null then a complete tree will be
     512      *                shown
     513      * @param $childrenMethod The method to call to get the children of the tree.  For example,
     514      *                        Children, AllChildrenIncludingDeleted, or AllHistoricalChildren
     515      */
     516     function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) {
     517         // Default childrenMethod and numChildrenMethod
     518         if (!$childrenMethod) $childrenMethod = 'AllChildrenIncludingDeleted';
     519         if (!$numChildrenMethod) $numChildrenMethod = 'numChildren';
     520
     521         // Get the tree root
     522         $obj = $rootID ? $this->getRecord($rootID) : singleton($className);
     523
     524         // Mark the nodes of the tree to return
     525         if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction);
     526
     527         $obj->markPartialTree($minNodeCount, $this, $childrenMethod, $numChildrenMethod);
     528
     529         // Ensure current page is exposed
     530         if($p = $this->currentPage()) $obj->markToExpose($p);
     531
     532         // NOTE: SiteTree/CMSMain coupling :-(
     533         SiteTree::prepopuplate_permission_cache('CanEditType', $obj->markedNodeIDs(), 'SiteTree::can_edit_multiple');
     534
     535         // getChildrenAsUL is a flexible and complex way of traversing the tree
     536         $titleEval = '
     537                     "<li id=\"record-$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" .
     538                     "<a href=\"" . Controller::join_links(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" class=\"" . $child->CMSTreeClasses($extraArg) . "\" title=\"' . _t('LeftAndMain.PAGETYPE','Page type: ') . '".$child->class."\" >" .
     539                     ($child->TreeTitle()) .
     540                     "</a>"
     541 ';
     542         $siteTree = $obj->getChildrenAsUL(
     543             "",
     544             $titleEval,
     545             $this,
     546             true,
     547             $childrenMethod,
     548             $numChildrenMethod,
     549             $minNodeCount
     550         );
     551
     552         // Wrap the root if needs be.
     553
     554         if(!$rootID) {
     555             $rootLink = $this->Link('show') . '/root';
     556
     557             // This lets us override the tree title with an extension
     558             if($this->hasMethod('getCMSTreeTitle') && $customTreeTitle = $this->getCMSTreeTitle()) {
     559                 $treeTitle = $customTreeTitle;
     560             } else {
     561                 $siteConfig = SiteConfig::current_site_config();
     562                 $treeTitle =  $siteConfig->Title;
     563             }
     564
     565             $siteTree = "<ul id=\"sitetree\" class=\"tree unformatted\"><li id=\"record-0\" class=\"Root nodelete\"><a href=\"$rootLink\"><strong>$treeTitle</strong></a>"
     566                 . $siteTree . "</li></ul>";
     567         }
     568
     569         return $siteTree;
     570     }
     571
     572     /**
     573      * Get a subtree underneath the request param 'ID'.
     574      * If ID = 0, then get the whole tree.
     575      */
     576     public function getsubtree($request) {
     577         // Get the tree
     578         $minNodeCount = (is_numeric($request->getVar('minNodeCount'))) ? $request->getVar('minNodeCount') : NULL;
     579         $tree = $this->getSiteTreeFor(
     580             $this->stat('tree_class'),
     581             $request->getVar('ID'),
     582             null,
     583             null,
     584             null,
     585             $minNodeCount
     586         );
     587
     588         // Trim off the outer tag
     589         $tree = preg_replace('!^[ \t\r\n]*<ul[^>]*>!','', $tree);
     590         $tree = preg_replace('!</ul[^>]*>[ \t\r\n]*$!','', $tree);
     591
     592         return $tree;
     593     }
     594
     595     /**
     596      * Allows you to returns a new data object to the tree (subclass of sitetree)
     597      * and updates the tree via javascript.
     598      */
     599     public function returnItemToUser($p) {
     600         if(Director::is_ajax()) {
     601             // Prepare the object for insertion.
     602             $parentID = (int) $p->ParentID;
     603             $id = $p->ID ? $p->ID : "new-$p->class-$p->ParentID";
     604             $treeTitle = Convert::raw2js($p->TreeTitle());
     605             $hasChildren = (is_numeric($id) && $p->AllChildren() && $p->AllChildren()->Count()) ? ' unexpanded' : '';
     606
     607             // Ensure there is definitly a node avaliable. if not, append to the home tree.
     608             $response = <<<JS
     609                 var tree = $('sitetree');
     610                 var newNode = tree.createTreeNode("$id", "$treeTitle", "{$p->class}{$hasChildren}");
     611                 node = tree.getTreeNodeByIdx($parentID);
     612                 if(!node) {
     613                     node = tree.getTreeNodeByIdx(0);
     614                 }
     615                 node.open();
     616                 node.appendTreeNode(newNode);
     617                 newNode.selectTreeNode();
     618 JS;
     619             FormResponse::add($response);
     620
     621             return FormResponse::respond();
     622         } else {
     623             Director::redirect('admin/' . self::$url_segment . '/show/' . $p->ID);
     624         }
     625     }
     626
     627     /**
     628      * Save and Publish page handler
     629      */
     630     public function save($urlParams, $form) {
     631         $className = $this->stat('tree_class');
     632
     633         $SQL_id = Convert::raw2sql($_REQUEST['ID']);
     634         if(substr($SQL_id,0,3) != 'new') {
     635             $record = DataObject::get_one($className, "\"$className\".\"ID\" = {$SQL_id}");
     636             if($record && !$record->canEdit()) return Security::permissionFailure($this);
     637         } else {
     638             if(!singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this);
     639             $record = $this->getNewItem($SQL_id, false);
     640         }
     641
     642         // We don't want to save a new version if there are no changes
     643         $dataFields_new = $form->Fields()->dataFields();
     644         $dataFields_old = $record->getAllFields();
     645         $changed = false;
     646         $hasNonRecordFields = false;
     647         foreach($dataFields_new as $datafield) {
     648             // if the form has fields not belonging to the record
     649             if(!isset($dataFields_old[$datafield->Name()])) {
     650                 $hasNonRecordFields = true;
     651             }
     652             // if field-values have changed
     653             if(!isset($dataFields_old[$datafield->Name()]) || $dataFields_old[$datafield->Name()] != $datafield->dataValue()) {
     654                 $changed = true;
     655             }
     656         }
     657
     658         if(!$changed && !$hasNonRecordFields) {
     659             // Tell the user we have saved even though we haven't, as not to confuse them
     660             if(is_a($record, "Page")) {
     661                 $record->Status = "Saved (update)";
     662             }
     663             FormResponse::status_message(_t('LeftAndMain.SAVEDUP',"Saved"), "good");
     664             FormResponse::update_status($record->Status);
     665             return FormResponse::respond();
     666         }
     667
     668         $form->dataFieldByName('ID')->Value = 0;
     669
     670         if(isset($urlParams['Sort']) && is_numeric($urlParams['Sort'])) {
     671             $record->Sort = $urlParams['Sort'];
     672         }
     673
     674         // HACK: This should be turned into something more general
     675         $originalClass = $record->ClassName;
     676         $originalStatus = $record->Status;
     677         $originalParentID = $record->ParentID;
     678
     679         $originalBrokenLinkValues = $record->HasBrokenLink.$record->HasBrokenFile;
     680
     681         $record->HasBrokenLink = 0;
     682         $record->HasBrokenFile = 0;
     683
     684         $record->writeWithoutVersion();
     685
     686         // HACK: This should be turned into something more general
     687         $originalURLSegment = $record->URLSegment;
     688
     689         $form->saveInto($record, true);
     690
     691         if(is_a($record, "Page")) {
     692             $record->Status = ($record->Status == "New page" || $record->Status == "Saved (new)") ? "Saved (new)" : "Saved (update)";
     693         }
     694
     695         if(Director::is_ajax()) {
     696             if($SQL_id != $record->ID) {
     697                 FormResponse::add("$('sitetree').setNodeIdx(\"{$SQL_id}\", \"$record->ID\");");
     698                 FormResponse::add("$('Form_EditForm').elements.ID.value = \"$record->ID\";");
     699             }
     700
     701             if($added = DataObjectLog::getAdded('SiteTree')) {
     702                 foreach($added as $page) {
     703                     if($page->ID != $record->ID) FormResponse::add($this->addTreeNodeJS($page));
     704                 }
     705             }
     706             if($deleted = DataObjectLog::getDeleted('SiteTree')) {
     707                 foreach($deleted as $page) {
     708                     if($page->ID != $record->ID) FormResponse::add($this->deleteTreeNodeJS($page));
     709                 }
     710             }
     711             if($changed = DataObjectLog::getChanged('SiteTree')) {
     712                 foreach($changed as $page) {
     713                     if($page->ID != $record->ID) {
     714                         $title = Convert::raw2js($page->TreeTitle());
     715                         FormResponse::add("$('sitetree').setNodeTitle($page->ID, \"$title\");");
     716                     }
     717                 }
     718             }
     719
     720             $message = _t('LeftAndMain.SAVEDUP');
     721
     722             // Update the class instance if necessary
     723             if($originalClass != $record->ClassName) {
     724                 $newClassName = $record->ClassName;
     725                 // The records originally saved attribute was overwritten by $form->saveInto($record) before.
     726                 // This is necessary for newClassInstance() to work as expected, and trigger change detection
     727                 // on the ClassName attribute
     728                 $record->setClassName($originalClass);
     729                 // Replace $record with a new instance
     730                 $record = $record->newClassInstance($newClassName);
     731
     732                 // update the tree icon
     733                 FormResponse::add("if(\$('sitetree').setNodeIcon) \$('sitetree').setNodeIcon($record->ID, '$originalClass', '$record->ClassName');");
     734             }
     735
     736             // HACK: This should be turned into somethign more general
     737             // Removed virtualpage test as we need to draft/published links when url is changed
     738             if( (/*$record->class == 'VirtualPage' &&*/ $originalURLSegment != $record->URLSegment) ||
     739                 ($originalClass != $record->ClassName) || self::$ForceReload == true) {
     740                 // avoid double loading by adding a uniqueness ID
     741                 FormResponse::add($str = "$('Form_EditForm').getPageFromServer($record->ID);", $str);
     742             }
     743
     744             // After reloading action
     745 //          if($originalStatus != $record->Status) {
     746 //              $message .= sprintf(_t('LeftAndMain.STATUSTO',"  Status changed to '%s'"),$record->Status);
     747 //          }
     748
     749             if($originalParentID != $record->ParentID) {
     750                 FormResponse::add("if(\$('sitetree').setNodeParentID) \$('sitetree').setNodeParentID($record->ID, $record->ParentID);");
     751             }
     752
     753
     754
     755             $record->write();
     756
     757             if( ($record->class != 'VirtualPage') && $originalURLSegment != $record->URLSegment) {
     758                 $message .= sprintf(_t('LeftAndMain.CHANGEDURL',"  Changed URL to '%s'"),$record->URLSegment);
     759                 FormResponse::add("\$('Form_EditForm').elements.URLSegment.value = \"$record->URLSegment\";");
     760                 FormResponse::add("\$('Form_EditForm_StageURLSegment').value = \"" . $record->AbsoluteLink() . "\";");
     761             }
     762
     763             if($virtualPages = DataObject::get("VirtualPage", "\"CopyContentFromID\" = $record->ID")) {
     764                 foreach($virtualPages as $page) {
     765                     if($page->ID != $record->ID) {
     766                         $title = Convert::raw2js($page->TreeTitle());
     767                         FormResponse::add("$('sitetree').setNodeTitle($page->ID, \"$title\");");
     768                     }
     769                 }
     770             }
     771
     772             // If there has been a change in the broken link values, reload the page
     773             if ($originalBrokenLinkValues != $record->HasBrokenLink.$record->HasBrokenFile) {
     774                 // avoid double loading by adding a uniqueness ID
     775                 FormResponse::add($str = "$('Form_EditForm').getPageFromServer($record->ID);", $str);
     776             }
     777
     778             // If the 'Save & Publish' button was clicked, also publish the page
     779             if (isset($urlParams['publish']) && $urlParams['publish'] == 1) {
     780                 $this->extend('onAfterSave', $record);
     781
     782                 $record->doPublish();
     783
     784                 // Update classname with original and get new instance (see above for explanation)
     785                 $record->setClassName($originalClass);
     786                 $publishedRecord = $record->newClassInstance($record->ClassName);
     787
     788                 return $this->tellBrowserAboutPublicationChange(
     789                     $publishedRecord,
     790                     sprintf(
     791                         _t(
     792                             'LeftAndMain.STATUSPUBLISHEDSUCCESS',
     793                             "Published '%s' successfully",
     794                             PR_MEDIUM,
     795                             'Status message after publishing a page, showing the page title'
     796                         ),
     797                         $record->Title
     798                     )
     799                 );
     800             } else {
     801                 // BUGFIX: Changed icon only shows after Save button is clicked twice http://support.silverstripe.com/gsoc/ticket/76
     802                 $title = Convert::raw2js($record->TreeTitle());
     803                 FormResponse::add("$('sitetree').setNodeTitle(\"$record->ID\", \"$title\");");
     804                 FormResponse::add($this->getActionUpdateJS($record));
     805                 FormResponse::status_message($message, "good");
     806                 FormResponse::update_status($record->Status);
     807
     808                 $this->extend('onAfterSave', $record);
     809
     810                 return FormResponse::respond();
     811             }
     812         }
     813     }
     814
     815     /**
     816      * Returns a javascript snippet that will update the actions of the main form
     817      *
     818      * @return string
     819      */
     820     public function getActionUpdateJS($record) {
     821         // Get the new action buttons
     822
     823         $tempForm = $this->getEditForm($record->ID);
     824         $actionList = '';
     825         foreach($tempForm->Actions() as $action) {
     826             $actionList .= $action->Field() . ' ';
     827         }
     828
     829         return "$('Form_EditForm').loadActionsFromString('" . Convert::raw2js($actionList) . "');";
     830     }
     831
     832     /**
     833      * Returns a javascript snippet to generate a tree node for the given page, if visible
     834      *
     835      * @return string
     836      */
     837     public function addTreeNodeJS($page, $select = false) {
     838         $parentID = (int)$page->ParentID;
     839         $title = Convert::raw2js($page->TreeTitle());
     840         $response = <<<JS
     841 var newNode = $('sitetree').createTreeNode($page->ID, "$title", "$page->class");
     842 var parentNode = $('sitetree').getTreeNodeByIdx($parentID);
     843 if(parentNode) parentNode.appendTreeNode(newNode);
     844 JS;
     845         $response .= ($select ? "newNode.selectTreeNode();\n" : "") ;
     846         return $response;
     847     }
     848     /**
     849      * Returns a javascript snippet to remove a tree node for the given page, if it exists.
     850      *
     851      * @return string
     852      */
     853     public function deleteTreeNodeJS($page) {
     854         $id = $page->ID ? $page->ID : $page->OldID;
     855         $response = <<<JS
     856 var node = $('sitetree').getTreeNodeByIdx($id);
     857 if(node && node.parentTreeNode) node.parentTreeNode.removeTreeNode(node);
     858 $('Form_EditForm').closeIfSetTo($id);
     859 JS;
     860
     861         // If we have that page selected currently, then clear that info from the session
     862         if(Session::get("{$this->class}.currentPage") == $id) {
     863             $this->setCurrentPageID(null);
     864         }
     865
     866         return $response;
     867     }
     868
     869     /**
     870      * Sets a static variable on this class which means the panel will be reloaded.
     871      */
     872     static function ForceReload(){
     873         self::$ForceReload = true;
     874     }
     875
     876     /**
     877      * Ajax handler for updating the parent of a tree node
     878      */
     879     public function ajaxupdateparent() {
     880         $id = $_REQUEST['ID'];
     881         $parentID = $_REQUEST['ParentID'];
     882         if($parentID == 'root'){
     883             $parentID = 0;
     884         }
     885         $_REQUEST['ajax'] = 1;
     886         $cleanupJS = '';
     887
     888         if (!Permission::check('SITETREE_REORGANISE') && !Permission::check('ADMIN')) {
     889             FormResponse::status_message(_t('LeftAndMain.CANT_REORGANISE',"You do not have permission to rearange the site tree. Your change was not saved."),"bad");
     890             return FormResponse::respond();
     891         }
     892
     893         if(is_numeric($id) && is_numeric($parentID) && $id != $parentID) {
     894             $node = DataObject::get_by_id($this->stat('tree_class'), $id);
     895             if($node){
     896                 if(!$node->canEdit() || ($node->hasMethod('checkDevPermisson') && !$node->checkDevPermisson(null,'canReorder'))){
     897                     return Security::permissionFailure($this);
     898                 }
     899
     900                 $node->ParentID = $parentID;
     901                 $node->Status = "Saved (update)";
     902                 $node->write();
     903
     904                 if(is_numeric($_REQUEST['CurrentlyOpenPageID'])) {
     905                     $currentPage = DataObject::get_by_id($this->stat('tree_class'), $_REQUEST['CurrentlyOpenPageID']);
     906                     if($currentPage) {
     907                         $cleanupJS = $currentPage->cmsCleanup_parentChanged();
     908                     }
     909                 }
     910
     911                 FormResponse::status_message(_t('LeftAndMain.SAVED','saved'), 'good');
     912                 if($cleanupJS) FormResponse::add($cleanupJS);
     913
     914             }else{
     915                 FormResponse::status_message(_t('LeftAndMain.PLEASESAVE',"Please Save Page: This page could not be upated because it hasn't been saved yet."),"good");
     916             }
     917
     918
     919             return FormResponse::respond();
     920         } else {
     921             user_error("Error in ajaxupdateparent request; id=$id, parentID=$parentID", E_USER_ERROR);
     922         }
     923     }
     924
     925     /**
     926      * Ajax handler for updating the order of a number of tree nodes
     927      * $_GET[ID]: An array of node ids in the correct order
     928      * $_GET[MovedNodeID]: The node that actually got moved
     929      */
     930     public function ajaxupdatesort() {
     931         $className = $this->stat('tree_class');
     932         $counter = 0;
     933         $js = '';
     934         $_REQUEST['ajax'] = 1;
     935
     936         if (!Permission::check('SITETREE_REORGANISE') && !Permission::check('ADMIN')) {
     937             FormResponse::status_message(_t('LeftAndMain.CANT_REORGANISE',"You do not have permission to rearange the site tree. Your change was not saved."),"bad");
     938             return FormResponse::respond();
     939         }
     940
     941         if(is_array($_REQUEST['ID'])) {
     942             if($_REQUEST['MovedNodeID']==0){ //Sorting root
     943                 $movedNode = DataObject::get($className, "\"ParentID\"=0");
     944             }else{
     945                 $movedNode = DataObject::get_by_id($className, $_REQUEST['MovedNodeID']);
     946             }
     947             foreach($_REQUEST['ID'] as $id) {
     948                 if($id == $movedNode->ID) {
     949                     $movedNode->Sort = ++$counter;
     950                     $movedNode->Status = "Saved (update)";
     951                     $movedNode->write();
     952
     953                     $title = Convert::raw2js($movedNode->TreeTitle());
     954                     $js .="$('sitetree').setNodeTitle($movedNode->ID, \"$title\");\n";
     955
     956                 // Nodes that weren't "actually moved" shouldn't be registered as having been edited; do a direct SQL update instead
     957                 } else if(is_numeric($id)) {
     958                     ++$counter;
     959                     DB::query("UPDATE \"$className\" SET \"Sort\" = $counter WHERE \"ID\" = '$id'");
     960                 }
     961             }
     962             FormResponse::status_message(_t('LeftAndMain.SAVED'), 'good');
     963         } else {
     964             FormResponse::error(_t('LeftAndMain.REQUESTERROR',"Error in request"));
     965         }
     966
     967         return FormResponse::respond();
     968     }
     969
     970     public function CanOrganiseSitetree() {
     971         return !Permission::check('SITETREE_REORGANISE') && !Permission::check('ADMIN') ? false : true;
     972     }
     973
     974     /**
     975      * Delete a number of items
     976      */
     977     public function deleteitems() {
     978         $ids = preg_split('! *, *!', $_REQUEST['csvIDs']);
     979
     980         $script = "st = \$('sitetree'); \n";
     981         foreach($ids as $id) {
     982             if(is_numeric($id)) {
     983                 $record = DataObject::get_by_id($this->stat('tree_class'), $id);
     984                 if($record && !$record->canDelete()) return Security::permissionFailure($this);
     985
     986                 DataObject::delete_by_id($this->stat('tree_class'), $id);
     987                 $script .= "node = st.getTreeNodeByIdx($id); if(node) node.parentTreeNode.removeTreeNode(node); $('Form_EditForm').closeIfSetTo($id); \n";
     988
     989             }
     990         }
     991
     992         FormResponse::add($script);
     993
     994         return FormResponse::respond();
     995     }
     996
     997     /**
     998      * Returns a placeholder form, used by {@link getEditForm()} if no record is selected.
     999      * Our javascript logic always requires a form to be present in the CMS interface.
    1000      *
    1001      * @return Form
    1002      */
    1003     function EmptyForm() {
    1004         $form = new Form(
    1005             $this,
    1006             "EditForm",
    1007             new FieldSet(
    1008                 new HeaderField(
    1009                     'WelcomeHeader',
    1010                     $this->getApplicationName()
    1011                 ),
    1012                 new LiteralField(
    1013                     'WelcomeText',
    1014                     sprintf('<p id="WelcomeMessage">%s %s. %s</p>',
    1015                         _t('LeftAndMain_right.ss.WELCOMETO','Welcome to'),
    1016                         $this->getApplicationName(),
    1017                         _t('CHOOSEPAGE','Please choose an item from the left.')
    1018                     )
    1019                 )
    1020             ),
    1021             new FieldSet()
    1022         );
    1023         $form->unsetValidator();
    1024
    1025         return $form;
    1026     }
    1027
    1028     public function EditForm() {
    1029         // Include JavaScript to ensure HtmlEditorField works.
    1030         HtmlEditorField::include_js();
    1031
    1032         if ($this->currentPageID() != 0) {
    1033             $record = $this->currentPage();
    1034             if(!$record) return false;
    1035             if($record && !$record->canView()) return Security::permissionFailure($this);
    1036         }
    1037         if ($this->hasMethod('getEditForm')) {
    1038             return $this->getEditForm($this->currentPageID());
    1039         }
    1040
    1041         return false;
    1042     }
    1043
    1044     public function myprofile() {
    1045         $form = $this->Member_ProfileForm();
    1046         return $this->customise(array(
    1047             'Form' => $form
    1048         ))->renderWith('BlankPage');
    1049     }
    1050
    1051     public function Member_ProfileForm() {
    1052         return new Member_ProfileForm($this, 'Member_ProfileForm', Member::currentUser());
    1053     }
    1054
    1055     public function printable() {
    1056         $id = $_REQUEST['ID'] ? $_REQUEST['ID'] : $this->currentPageID();
    1057
    1058         if($id) $form = $this->getEditForm($id);
    1059         $form->transform(new PrintableTransformation());
    1060         $form->actions = null;
    1061
    1062         Requirements::clear();
    1063         Requirements::css(CMS_DIR . '/css/LeftAndMain_printable.css');
    1064         return array(
    1065             "PrintForm" => $form
    1066         );
    1067     }
    1068
    1069     public function currentPageID() {
    1070         if(isset($_REQUEST['ID']) && is_numeric($_REQUEST['ID']))   {
    1071             return $_REQUEST['ID'];
    1072         } elseif (isset($this->urlParams['ID']) && is_numeric($this->urlParams['ID'])) {
    1073             return $this->urlParams['ID'];
    1074         } elseif(Session::get("{$this->class}.currentPage")) {
    1075             return Session::get("{$this->class}.currentPage");
    1076         } else {
    1077             return null;
    1078         }
    1079     }
    1080
    1081     public function setCurrentPageID($id) {
    1082         Session::set("{$this->class}.currentPage", $id);
    1083     }
    1084
    1085     public function currentPage() {
    1086         return $this->getRecord($this->currentPageID());
    1087     }
    1088
    1089     public function isCurrentPage(DataObject $page) {
    1090         return $page->ID == Session::get("{$this->class}.currentPage");
    1091     }
    1092
    1093     /**
    1094      * Get the staus of a certain page and version.
    1095      *
    1096      * This function is used for concurrent editing, and providing alerts
    1097      * when multiple users are editing a single page. It echoes a json
    1098      * encoded string to the UA.
    1099      */
    1100
    1101     /**
    1102      * Return the CMS's HTML-editor toolbar
    1103      */
    1104     public function EditorToolbar() {
    1105         return SS_Object::create('HtmlEditorField_Toolbar', $this, "EditorToolbar");
    1106     }
    1107
    1108     /**
    1109      * Return the version number of this application.
    1110      * Uses the subversion path information in <mymodule>/silverstripe_version
    1111      * (automacially replaced $URL$ placeholder).
    1112      *
    1113      * @return string
    1114      */
    1115     public function CMSVersion() {
    1116         if (file_exists(BASE_PATH . '/webylon/.version'))
    1117             return file_get_contents(BASE_PATH . '/webylon/.version');
    1118
    1119         $sapphireVersionFile = file_get_contents(BASE_PATH . '/sapphire/silverstripe_version');
    1120         $cmsVersionFile = file_get_contents(BASE_PATH . '/cms/silverstripe_version');
    1121
    1122         $sapphireVersion = $this->versionFromVersionFile($sapphireVersionFile);
    1123         $cmsVersion = $this->versionFromVersionFile($cmsVersionFile);
    1124
    1125         if($sapphireVersion == $cmsVersion) {
    1126             return $sapphireVersion;
    1127         }   else {
    1128             return "cms: $cmsVersion, sapphire: $sapphireVersion";
    1129         }
    1130     }
    1131
    1132     /**
    1133      * Return the version from the content of a silverstripe_version file
    1134      */
    1135     public function versionFromVersionFile($fileContent) {
    1136         if(preg_match('/\/trunk\/silverstripe_version/', $fileContent)) {
    1137             return "trunk";
    1138         } else {
    1139             preg_match("/\/(?:branches|tags\/rc|tags\/beta|tags\/alpha|tags)\/([A-Za-z0-9._-]+)\/silverstripe_version/", $fileContent, $matches);
    1140             return ($matches) ? $matches[1] : null;
    1141         }
    1142     }
    1143
    1144     /**
    1145      * @return array
    1146      */
    1147     function SwitchView() {
    1148         if($page = $this->currentPage()) {
    1149             $nav = SilverStripeNavigator::get_for_record($page);
    1150             return $nav['items'];
    1151         }
    1152     }
    1153
    1154     /**
    1155      * The application name. Customisable by calling
    1156      * LeftAndMain::setApplicationName() - the first parameter.
    1157      *
    1158      * @var String
    1159      */
    1160     static $application_name = 'SilverStripe CMS';
    1161
    1162     /**
    1163      * The application logo text. Customisable by calling
    1164      * LeftAndMain::setApplicationName() - the second parameter.
    1165      *
    1166      * @var String
    1167      */
    1168     static $application_logo_text = 'SilverStripe';
    1169
    1170     /**
    1171      * Set the application name, and the logo text.
    1172      *
    1173      * @param String $name The application name
    1174      * @param String $logoText The logo text
    1175      */
    1176     static $application_link = "http://www.silverstripe.org/";
    1177     static function setApplicationName($name, $logoText = null, $link = null) {
    1178         self::$application_name = $name;
    1179         self::$application_logo_text = $logoText ? $logoText : $name;
    1180         if($link) self::$application_link = $link;
    1181     }
    1182
    1183     /**
    1184      * Get the application name.
    1185      * @return String
    1186      */
    1187     function getApplicationName() {
    1188         return self::$application_name;
    1189     }
    1190
    1191     /**
    1192      * Get the application logo text.
    1193      * @return String
    1194      */
    1195     function getApplicationLogoText() {
    1196         return self::$application_logo_text;
    1197     }
    1198     function ApplicationLink() {
    1199         return self::$application_link;
    1200     }
    1201
    1202     /**
    1203      * Return the title of the current section, as shown on the main menu
    1204      */
    1205     function SectionTitle() {
    1206         // Get menu - use obj() to cache it in the same place as the template engine
    1207         $menu = $this->obj('MainMenu');
    1208
    1209         foreach($menu as $menuItem) {
    1210             if($menuItem->LinkingMode == 'current') return $menuItem->Title;
    1211         }
    1212     }
    1213
    1214     /**
    1215      * The application logo path. Customisable by calling
    1216      * LeftAndMain::setLogo() - the first parameter.
    1217      *
    1218      * @var unknown_type
    1219      */
    1220     static $application_logo = 'cms/images/mainmenu/logo.gif';
    1221
    1222     /**
    1223      * The application logo style. Customisable by calling
    1224      * LeftAndMain::setLogo() - the second parameter.
    1225      *
    1226      * @var String
    1227      */
    1228     static $application_logo_style = '';
    1229
    1230     /**
    1231      * Set the CMS application logo.
    1232      *
    1233      * @param String $logo Relative path to the logo
    1234      * @param String $logoStyle Custom CSS styles for the logo
    1235      *                          e.g. "border: 1px solid red; padding: 5px;"
    1236      */
    1237     static function setLogo($logo, $logoStyle) {
    1238         self::$application_logo = $logo;
    1239         self::$application_logo_style = $logoStyle;
    1240         self::$application_logo_text = '';
    1241     }
    1242
    1243     protected static $loading_image = 'cms/images/loading.gif';
    1244
    1245     /**
    1246      * Set the image shown when the CMS is loading.
    1247      */
    1248     static function set_loading_image($loadingImage) {
    1249         self::$loading_image = $loadingImage;
    1250     }
    1251
    1252     function LoadingImage() {
    1253         return self::$loading_image;
    1254     }
    1255
    1256     function LogoStyle() {
    1257         return "background: url(" . self::$application_logo . ") no-repeat; " . self::$application_logo_style;
    1258     }
    1259
    1260     /**
    1261      * Return the base directory of the tiny_mce codebase
    1262      */
    1263     function MceRoot() {
    1264         return MCE_ROOT;
    1265     }
    1266
    1267     /**
    1268      * Use this as an action handler for custom CMS buttons.
    1269      */
    1270     function callPageMethod($data, $form) {
    1271         $methodName = $form->buttonClicked()->extraData();
    1272         $record = $this->currentPage();
    1273         if(!$record) return false;
    1274
    1275         return $record->$methodName($data, $form);
    1276     }
    1277
    1278     /**
    1279      * Register the given javascript file as required in the CMS.
    1280      * Filenames should be relative to the base, eg, SAPPHIRE_DIR . '/javascript/loader.js'
    1281      */
    1282     public static function require_javascript($file) {
    1283         self::$extra_requirements['javascript'][] = array($file);
    1284     }
    1285
    1286     /**
    1287      * Register the given stylesheet file as required.
    1288      *
    1289      * @param $file String Filenames should be relative to the base, eg, THIRDPARTY_DIR . '/tree/tree.css'
    1290      * @param $media String Comma-separated list of media-types (e.g. "screen,projector")
    1291      * @see http://www.w3.org/TR/REC-CSS2/media.html
    1292      */
    1293     public static function require_css($file, $media = null) {
    1294         self::$extra_requirements['css'][] = array($file, $media);
    1295     }
    1296
    1297     /**
    1298      * Register the given "themeable stylesheet" as required.
    1299      * Themeable stylesheets have globally unique names, just like templates and PHP files.
    1300      * Because of this, they can be replaced by similarly named CSS files in the theme directory.
    1301      *
    1302      * @param $name String The identifier of the file.  For example, css/MyFile.css would have the identifier "MyFile"
    1303      * @param $media String Comma-separated list of media-types (e.g. "screen,projector")
    1304      */
    1305     static function require_themed_css($name, $media = null) {
    1306         self::$extra_requirements['themedcss'][] = array($name, $media);
    1307     }
    1308 }
    1309
    1310

[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. -