2011-03-23 10:51:00 +01:00
< ? php
/**
* LeftAndMain is the parent class of all the two - pane views in the CMS .
* If you are wanting to add more areas to the CMS , you can do it by subclassing LeftAndMain .
*
* This is essentially an abstract class which should be subclassed .
* See { @ link CMSMain } for a good example .
*
* @ package cms
* @ subpackage core
*/
2012-03-05 16:07:20 +01:00
class LeftAndMain extends Controller implements PermissionProvider {
2011-03-23 10:51:00 +01:00
/**
* The 'base' url for CMS administration areas .
* Note that if this is changed , many javascript
* behaviours need to be updated with the correct url
*
* @ var string $url_base
*/
static $url_base = " admin " ;
2011-05-20 01:29:40 +02:00
/**
* The current url segment attached to the LeftAndMain instance
*
* @ var string
*/
2011-03-23 10:51:00 +01:00
static $url_segment ;
2011-05-20 01:29:40 +02:00
/**
* @ var string
*/
2011-03-23 10:51:00 +01:00
static $url_rule = '/$Action/$ID/$OtherID' ;
2011-05-20 01:29:40 +02:00
/**
* @ var string
*/
2011-03-23 10:51:00 +01:00
static $menu_title ;
2011-05-20 01:29:40 +02:00
/**
* @ var int
*/
2011-03-23 10:51:00 +01:00
static $menu_priority = 0 ;
2011-05-20 01:29:40 +02:00
/**
* @ var int
*/
2011-03-23 10:51:00 +01:00
static $url_priority = 50 ;
/**
2011-05-20 01:29:40 +02:00
* A subclass of { @ link DataObject } .
*
* Determines what is managed in this interface , through
* { @ link getEditForm ()} and other logic .
*
* @ var string
2011-03-23 10:51:00 +01:00
*/
static $tree_class = null ;
/**
2011-05-20 01:29:40 +02:00
* The url used for the link in the Help tab in the backend
*
* @ var string
*/
2012-06-07 03:46:47 +02:00
static $help_link = 'http://3.0.userhelp.silverstripe.org' ;
2011-03-23 10:51:00 +01:00
2011-05-20 01:29:40 +02:00
/**
* @ var array
*/
2011-03-23 10:51:00 +01:00
static $allowed_actions = array (
'index' ,
2011-06-09 03:49:52 +02:00
'save' ,
2011-03-23 10:51:00 +01:00
'savetreenode' ,
'getsubtree' ,
'printable' ,
'show' ,
2012-05-23 07:15:17 +02:00
'ping' ,
2011-03-23 10:51:00 +01:00
'EditorToolbar' ,
'EditForm' ,
'AddForm' ,
'batchactions' ,
'BatchActionsForm' ,
'Member_ProfileForm' ,
);
2012-03-05 16:07:20 +01:00
/**
* @ var Array Codes which are required from the current user to view this controller .
* If multiple codes are provided , all of them are required .
* All CMS controllers require " CMS_ACCESS_LeftAndMain " as a baseline check ,
* and fall back to " CMS_ACCESS_<class> " if no permissions are defined here .
* See { @ link canView ()} for more details on permission checks .
*/
static $required_permission_codes ;
2011-03-23 10:51:00 +01:00
/**
2011-05-20 01:29:40 +02:00
* Register additional requirements through the { @ link Requirements } class .
2011-03-23 10:51:00 +01:00
* Used mainly to work around the missing " lazy loading " functionality
* for getting css / javascript required after an ajax - call ( e . g . loading the editform ) .
*
* @ var array $extra_requirements
*/
protected static $extra_requirements = array (
'javascript' => array (),
'css' => array (),
'themedcss' => array (),
);
2012-03-24 03:19:02 +01:00
/**
2012-05-18 07:36:06 +02:00
* @ var PjaxResponseNegotiator
2012-03-24 03:19:02 +01:00
*/
protected $responseNegotiator ;
2011-03-23 10:51:00 +01:00
/**
* @ param Member $member
* @ return boolean
*/
function canView ( $member = null ) {
2012-03-05 16:07:20 +01:00
if ( ! $member && $member !== FALSE ) $member = Member :: currentUser ();
2011-03-23 10:51:00 +01:00
// cms menus only for logged-in members
if ( ! $member ) return false ;
2011-04-15 11:35:30 +02:00
// alternative extended checks
2011-03-23 10:51:00 +01:00
if ( $this -> hasMethod ( 'alternateAccessCheck' )) {
$alternateAllowed = $this -> alternateAccessCheck ();
if ( $alternateAllowed === FALSE ) return false ;
}
2012-03-05 16:07:20 +01:00
// Check for "CMS admin" permission
if ( Permission :: checkMember ( $member , " CMS_ACCESS_LeftAndMain " )) return true ;
// Check for LeftAndMain sub-class permissions
$codes = array ();
$extraCodes = $this -> stat ( 'required_permission_codes' );
if ( $extraCodes !== false ) { // allow explicit FALSE to disable subclass check
if ( $extraCodes ) $codes = array_merge ( $codes , ( array ) $extraCodes );
else $codes [] = " CMS_ACCESS_ $this->class " ;
2011-03-23 10:51:00 +01:00
}
2012-03-05 16:07:20 +01:00
foreach ( $codes as $code ) if ( ! Permission :: checkMember ( $member , $code )) return false ;
2011-03-23 10:51:00 +01:00
return true ;
}
/**
2011-04-15 11:35:30 +02:00
* @ uses LeftAndMainExtension -> init ()
* @ uses LeftAndMainExtension -> accessedCMS ()
2011-03-23 10:51:00 +01:00
* @ uses CMSMenu
*/
function init () {
parent :: init ();
SSViewer :: setOption ( 'rewriteHashlinks' , false );
// set language
$member = Member :: currentUser ();
if ( ! empty ( $member -> Locale )) i18n :: set_locale ( $member -> Locale );
if ( ! empty ( $member -> DateFormat )) i18n :: set_date_format ( $member -> DateFormat );
if ( ! empty ( $member -> TimeFormat )) i18n :: set_time_format ( $member -> TimeFormat );
// can't be done in cms/_config.php as locale is not set yet
CMSMenu :: add_link (
'Help' ,
2012-04-14 00:16:22 +02:00
_t ( 'LeftAndMain.HELP' , 'Help' , 'Menu title' ),
2011-03-23 10:51:00 +01:00
self :: $help_link
);
2011-04-15 11:35:30 +02:00
// Allow customisation of the access check by a extension
2012-05-23 11:50:02 +02:00
// Also all the canView() check to execute Controller::redirect()
2011-03-23 10:51:00 +01:00
if ( ! $this -> canView () && ! $this -> response -> isFinished ()) {
// When access /admin/, we should try a redirect to another part of the admin rather than be locked out
$menu = $this -> MainMenu ();
foreach ( $menu as $candidate ) {
if (
$candidate -> Link &&
$candidate -> Link != $this -> Link ()
&& $candidate -> MenuItem -> controller
&& singleton ( $candidate -> MenuItem -> controller ) -> canView ()
) {
2012-05-23 11:50:02 +02:00
return $this -> redirect ( $candidate -> Link );
2011-03-23 10:51:00 +01:00
}
}
if ( Member :: currentUser ()) {
Session :: set ( " BackURL " , null );
}
// if no alternate menu items have matched, return a permission error
$messageSet = array (
'default' => _t ( 'LeftAndMain.PERMDEFAULT' , " Please choose an authentication method and enter your credentials to access the CMS. " ),
'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 " ),
'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. " ),
);
return Security :: permissionFailure ( $this , $messageSet );
}
// Don't continue if there's already been a redirection request.
2012-05-23 11:50:02 +02:00
if ( $this -> redirectedTo ()) return ;
2011-03-23 10:51:00 +01:00
// Audit logging hook
2012-04-05 14:44:42 +02:00
if ( empty ( $_REQUEST [ 'executeForm' ]) && ! $this -> request -> isAjax ()) $this -> extend ( 'accessedCMS' );
2011-08-05 05:46:57 +02:00
2011-12-12 23:41:49 +01:00
// Set the members html editor config
HtmlEditorConfig :: set_active ( Member :: currentUser () -> getHtmlEditorConfigForCMS ());
2011-03-23 10:51:00 +01:00
2011-12-13 14:56:35 +01:00
// Set default values in the config if missing. These things can't be defined in the config
// file because insufficient information exists when that is being processed
$htmlEditorConfig = HtmlEditorConfig :: get_active ();
$htmlEditorConfig -> setOption ( 'language' , i18n :: get_tinymce_lang ());
if ( ! $htmlEditorConfig -> getOption ( 'content_css' )) {
$cssFiles = array ();
2012-03-24 04:38:57 +01:00
$cssFiles [] = FRAMEWORK_ADMIN_DIR . '/css/editor.css' ;
2011-12-13 14:56:35 +01:00
// Use theme from the site config
if ( class_exists ( 'SiteConfig' ) && ( $config = SiteConfig :: current_site_config ()) && $config -> Theme ) {
$theme = $config -> Theme ;
} elseif ( SSViewer :: current_theme ()) {
$theme = SSViewer :: current_theme ();
} else {
$theme = false ;
}
if ( $theme ) $cssFiles [] = THEMES_DIR . " / { $theme } /css/editor.css " ;
else if ( project ()) $cssFiles [] = project () . '/css/editor.css' ;
// Remove files that don't exist
foreach ( $cssFiles as $k => $cssFile ) {
if ( ! file_exists ( BASE_PATH . '/' . $cssFile )) unset ( $cssFiles [ $k ]);
}
$htmlEditorConfig -> setOption ( 'content_css' , implode ( ',' , $cssFiles ));
}
2012-01-06 12:00:04 +01:00
// Using uncompressed files as they'll be processed by JSMin in the Requirements class.
// Not as effective as other compressors or pre-compressed+finetuned files,
// but overall the unified minification into a single file brings more performance benefits
// than a couple of saved bytes (after gzip) in individual files.
// We also re-compress already compressed files through JSMin as this causes weird runtime bugs.
2011-03-23 10:51:00 +01:00
Requirements :: combine_files (
2011-04-25 11:24:55 +02:00
'lib.js' ,
2011-03-23 10:51:00 +01:00
array (
2011-04-25 11:24:55 +02:00
THIRDPARTY_DIR . '/jquery/jquery.js' ,
2012-03-24 04:38:57 +01:00
FRAMEWORK_DIR . '/javascript/jquery-ondemand/jquery.ondemand.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/lib.js' ,
2011-04-25 11:24:55 +02:00
THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js' ,
THIRDPARTY_DIR . '/json-js/json2.js' ,
THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js' ,
THIRDPARTY_DIR . '/jquery-cookie/jquery.cookie.js' ,
2011-07-15 10:35:41 +02:00
THIRDPARTY_DIR . '/jquery-query/jquery.query.js' ,
2012-03-02 13:42:15 +01:00
THIRDPARTY_DIR . '/jquery-form/jquery.form.js' ,
2012-03-24 04:38:57 +01:00
FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.js' ,
FRAMEWORK_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js' ,
FRAMEWORK_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js' ,
FRAMEWORK_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js' ,
FRAMEWORK_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.js' ,
FRAMEWORK_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.adapter.jquery.js' ,
FRAMEWORK_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.html4.js' ,
2011-04-25 11:24:55 +02:00
THIRDPARTY_DIR . '/jstree/jquery.jstree.js' ,
2012-03-24 04:38:57 +01:00
FRAMEWORK_ADMIN_DIR . '/thirdparty/chosen/chosen/chosen.jquery.js' ,
FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-hoverIntent/jquery.hoverIntent.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/jquery-changetracker/lib/jquery.changetracker.js' ,
FRAMEWORK_DIR . '/javascript/TreeDropdownField.js' ,
FRAMEWORK_DIR . '/javascript/DateField.js' ,
FRAMEWORK_DIR . '/javascript/HtmlEditorField.js' ,
FRAMEWORK_DIR . '/javascript/TabSet.js' ,
FRAMEWORK_DIR . '/javascript/i18n.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/ssui.core.js' ,
FRAMEWORK_DIR . '/javascript/GridField.js' ,
2011-03-23 10:51:00 +01:00
)
);
2012-06-14 04:38:23 +02:00
if ( Director :: isDev ()) Requirements :: javascript ( FRAMEWORK_ADMIN_DIR . '/javascript/leaktools.js' );
2011-12-13 14:56:35 +01:00
HTMLEditorField :: include_js ();
2011-03-23 10:51:00 +01:00
Requirements :: combine_files (
'leftandmain.js' ,
2012-01-06 12:00:04 +01:00
array_unique ( array_merge (
array (
2012-03-24 04:38:57 +01:00
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Content.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.EditForm.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Menu.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.AddForm.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Preview.js' ,
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.BatchActions.js' ,
2012-06-13 11:59:25 +02:00
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.FieldHelp.js' ,
2012-01-06 12:00:04 +01:00
),
2012-03-24 04:38:57 +01:00
Requirements :: add_i18n_javascript ( FRAMEWORK_DIR . '/javascript/lang' , true , true ),
Requirements :: add_i18n_javascript ( FRAMEWORK_ADMIN_DIR . '/javascript/lang' , true , true )
2012-01-06 12:00:04 +01:00
))
2011-03-23 10:51:00 +01:00
);
2012-01-06 12:00:04 +01:00
2012-06-06 11:49:32 +02:00
// TODO Confuses jQuery.ondemand through document.write()
// if (Director::isDev()) {
// Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js');
// }
2012-05-14 06:00:42 +02:00
2012-03-24 04:38:57 +01:00
Requirements :: css ( FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.css' );
2012-01-06 10:46:27 +01:00
Requirements :: css ( THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css' );
2012-03-24 04:38:57 +01:00
Requirements :: css ( FRAMEWORK_ADMIN_DIR . '/thirdparty/chosen/chosen/chosen.css' );
2012-01-06 10:46:27 +01:00
Requirements :: css ( THIRDPARTY_DIR . '/jstree/themes/apple/style.css' );
2012-03-24 04:38:57 +01:00
Requirements :: css ( FRAMEWORK_DIR . '/css/TreeDropdownField.css' );
Requirements :: css ( FRAMEWORK_ADMIN_DIR . '/css/screen.css' );
Requirements :: css ( FRAMEWORK_DIR . '/css/GridField.css' );
2012-01-03 14:49:35 +01:00
// Browser-specific requirements
$ie = isset ( $_SERVER [ 'HTTP_USER_AGENT' ]) ? strpos ( $_SERVER [ 'HTTP_USER_AGENT' ], 'MSIE' ) : false ;
if ( $ie ) {
$version = substr ( $_SERVER [ 'HTTP_USER_AGENT' ], $ie + 5 , 3 );
2012-03-24 04:38:57 +01:00
if ( $version == 7 ) Requirements :: css ( FRAMEWORK_ADMIN_DIR . '/css/ie7.css' );
else if ( $version == 8 ) Requirements :: css ( FRAMEWORK_ADMIN_DIR . '/css/ie8.css' );
2012-01-03 14:49:35 +01:00
}
2012-05-23 07:15:17 +02:00
// Custom requirements
2011-10-11 09:55:58 +02:00
foreach ( self :: $extra_requirements [ 'javascript' ] as $file ) {
Requirements :: javascript ( $file [ 0 ]);
}
foreach ( self :: $extra_requirements [ 'css' ] as $file ) {
Requirements :: css ( $file [ 0 ], $file [ 1 ]);
}
foreach ( self :: $extra_requirements [ 'themedcss' ] as $file ) {
Requirements :: themedCSS ( $file [ 0 ], $file [ 1 ]);
}
2011-03-23 10:51:00 +01:00
$dummy = null ;
$this -> extend ( 'init' , $dummy );
// The user's theme shouldn't affect the CMS, if, for example, they have replaced
// TableListField.ss or Form.ss.
SSViewer :: set_theme ( null );
}
2011-06-09 03:49:52 +02:00
2012-03-27 06:04:11 +02:00
function handleRequest ( SS_HTTPRequest $request , DataModel $model = null ) {
2011-06-09 05:47:59 +02:00
$title = $this -> Title ();
2011-09-26 05:55:59 +02:00
$response = parent :: handleRequest ( $request , $model );
2011-12-20 17:06:11 +01:00
if ( ! $response -> getHeader ( 'X-Controller' )) $response -> addHeader ( 'X-Controller' , $this -> class );
if ( ! $response -> getHeader ( 'X-Title' )) $response -> addHeader ( 'X-Title' , $title );
2011-06-09 05:47:59 +02:00
2011-06-09 03:49:52 +02:00
return $response ;
}
2012-03-24 03:19:02 +01:00
/**
* Overloaded redirection logic to trigger a fake redirect on ajax requests .
* While this violates HTTP principles , its the only way to work around the
* fact that browsers handle HTTP redirects opaquely , no intervention via JS is possible .
* In isolation , that ' s not a problem - but combined with history . pushState ()
* it means we would request the same redirection URL twice if we want to update the URL as well .
* See LeftAndMain . js for the required jQuery ajaxComplete handlers .
*/
function redirect ( $url , $code = 302 ) {
if ( $this -> request -> isAjax ()) {
$this -> response -> addHeader ( 'X-ControllerURL' , $url );
2012-06-14 20:20:29 +02:00
if ( $this -> request -> getHeader ( 'X-Pjax' ) && ! $this -> response -> getHeader ( 'X-Pjax' )) {
$this -> response -> addHeader ( 'X-Pjax' , $this -> request -> getHeader ( 'X-Pjax' ));
}
2012-03-24 03:19:02 +01:00
return '' ; // Actual response will be re-requested by client
} else {
parent :: redirect ( $url , $code );
}
}
2011-06-09 03:49:52 +02:00
function index ( $request ) {
2012-03-24 03:19:02 +01:00
return $this -> getResponseNegotiator () -> respond ( $request );
2011-06-09 03:49:52 +02:00
}
2011-03-23 10:51:00 +01:00
2012-05-23 07:15:17 +02:00
/**
* admin / ping can be visited with ajax to keep a session alive .
* This is used in the CMS .
*/
function ping () {
return 1 ;
}
2011-03-23 10:51:00 +01:00
/**
* If this is set to true , the " switchView " context in the
* template is shown , with links to the staging and publish site .
*
* @ return boolean
*/
function ShowSwitchView () {
return false ;
}
2011-05-20 01:29:40 +02:00
2011-03-23 10:51:00 +01:00
//------------------------------------------------------------------------------------------//
// Main controllers
/**
* You should implement a Link () function in your subclass of LeftAndMain ,
* to point to the URL of that particular controller .
*
* @ return string
*/
public function Link ( $action = null ) {
// Handle missing url_segments
2012-05-15 21:27:32 +02:00
if ( ! $this -> stat ( 'url_segment' , true )) self :: $url_segment = $this -> class ;
$link = Controller :: join_links (
2011-03-23 10:51:00 +01:00
$this -> stat ( 'url_base' , true ),
$this -> stat ( 'url_segment' , true ),
'/' , // trailing slash needed if $action is null!
" $action "
);
2012-05-15 21:27:32 +02:00
$this -> extend ( 'updateLink' , $link );
return $link ;
2011-03-23 10:51:00 +01:00
}
2012-03-12 13:41:22 +01:00
2011-03-23 10:51:00 +01:00
/**
* Returns the menu title for the given LeftAndMain subclass .
* Implemented static so that we can get this value without instantiating an object .
* Menu title is * not * internationalised .
*/
static function menu_title_for_class ( $class ) {
2012-04-16 01:04:55 +02:00
$title = Config :: inst () -> get ( $class , 'menu_title' , Config :: FIRST_SET );
2011-03-23 10:51:00 +01:00
if ( ! $title ) $title = preg_replace ( '/Admin$/' , '' , $class );
return $title ;
}
2011-06-08 01:35:56 +02:00
2011-03-23 10:51:00 +01:00
public function show ( $request ) {
// TODO Necessary for TableListField URLs to work properly
if ( $request -> param ( 'ID' )) $this -> setCurrentPageID ( $request -> param ( 'ID' ));
2012-03-24 03:19:02 +01:00
return $this -> getResponseNegotiator () -> respond ( $request );
}
/**
* Caution : Volatile API .
*
2012-05-18 07:36:06 +02:00
* @ return PjaxResponseNegotiator
2012-03-24 03:19:02 +01:00
*/
2012-05-08 07:40:05 +02:00
public function getResponseNegotiator () {
2012-03-24 03:19:02 +01:00
if ( ! $this -> responseNegotiator ) {
$controller = $this ;
2012-06-13 00:27:03 +02:00
$this -> responseNegotiator = new PjaxResponseNegotiator (
array (
'CurrentForm' => function () use ( & $controller ) {
return $controller -> getEditForm () -> forTemplate ();
},
'Content' => function () use ( & $controller ) {
return $controller -> renderWith ( $controller -> getTemplatesWithSuffix ( '_Content' ));
},
'Breadcrumbs' => function () use ( & $controller ) {
return $controller -> renderWith ( 'CMSBreadcrumbs' );
},
'default' => function () use ( & $controller ) {
return $controller -> renderWith ( $controller -> getViewer ( 'show' ));
}
),
$this -> response
);
2011-03-23 10:51:00 +01:00
}
2012-03-24 03:19:02 +01:00
return $this -> responseNegotiator ;
2011-03-23 10:51:00 +01:00
}
//------------------------------------------------------------------------------------------//
// Main UI components
/**
* Returns the main menu of the CMS . This is also used by init ()
* to work out which sections the user has access to .
*
2012-05-03 13:49:19 +02:00
* @ param Boolean
2011-10-26 08:09:04 +02:00
* @ return SS_List
2011-03-23 10:51:00 +01:00
*/
2012-05-03 13:49:19 +02:00
public function MainMenu ( $cached = true ) {
if ( ! isset ( $this -> _cache_MainMenu ) || ! $cached ) {
// Don't accidentally return a menu if you're not logged in - it's used to determine access.
if ( ! Member :: currentUser ()) return new ArrayList ();
// Encode into DO set
$menu = new ArrayList ();
$menuItems = CMSMenu :: get_viewable_menu_items ();
if ( $menuItems ) {
foreach ( $menuItems as $code => $menuItem ) {
// alternate permission checks (in addition to LeftAndMain->canView())
if (
isset ( $menuItem -> controller )
&& $this -> hasMethod ( 'alternateMenuDisplayCheck' )
&& ! $this -> alternateMenuDisplayCheck ( $menuItem -> controller )
) {
continue ;
}
2011-03-23 10:51:00 +01:00
2012-05-03 13:49:19 +02:00
$linkingmode = " link " ;
if ( $menuItem -> controller && get_class ( $this ) == $menuItem -> controller ) {
2011-07-28 06:57:41 +02:00
$linkingmode = " current " ;
2012-05-03 13:49:19 +02:00
} else if ( strpos ( $this -> Link (), $menuItem -> url ) !== false ) {
if ( $this -> Link () == $menuItem -> url ) {
$linkingmode = " current " ;
// default menu is the one with a blank {@link url_segment}
} else if ( singleton ( $menuItem -> controller ) -> stat ( 'url_segment' ) == '' ) {
if ( $this -> Link () == $this -> stat ( 'url_base' ) . '/' ) {
$linkingmode = " current " ;
}
} else {
2011-07-28 06:57:41 +02:00
$linkingmode = " current " ;
}
2012-05-03 13:49:19 +02:00
}
// already set in CMSMenu::populate_menu(), but from a static pre-controller
// context, so doesn't respect the current user locale in _t() calls - as a workaround,
// we simply call LeftAndMain::menu_title_for_class() again
// if we're dealing with a controller
if ( $menuItem -> controller ) {
$defaultTitle = LeftAndMain :: menu_title_for_class ( $menuItem -> controller );
$title = _t ( " { $menuItem -> controller } .MENUTITLE " , $defaultTitle );
2011-07-28 06:57:41 +02:00
} else {
2012-05-03 13:49:19 +02:00
$title = $menuItem -> title ;
2011-07-28 06:57:41 +02:00
}
2012-05-03 13:49:19 +02:00
$menu -> push ( new ArrayData ( array (
" MenuItem " => $menuItem ,
" Title " => Convert :: raw2xml ( $title ),
" Code " => DBField :: create_field ( 'Text' , $code ),
" Link " => $menuItem -> url ,
" LinkingMode " => $linkingmode
)));
2011-03-23 10:51:00 +01:00
}
}
2012-05-03 13:49:19 +02:00
$this -> _cache_MainMenu = $menu ;
2011-03-23 10:51:00 +01:00
}
2011-07-28 06:57:41 +02:00
2012-05-03 13:49:19 +02:00
return $this -> _cache_MainMenu ;
2011-03-23 10:51:00 +01:00
}
2011-04-15 04:12:57 +02:00
public function Menu () {
return $this -> renderWith ( $this -> getTemplatesWithSuffix ( '_Menu' ));
2011-03-23 10:51:00 +01:00
}
2012-05-03 13:49:19 +02:00
/**
* @ todo Wrap in CMSMenu instance accessor
* @ return ArrayData A single menu entry ( see { @ link MainMenu })
*/
public function MenuCurrentItem () {
$items = $this -> MainMenu ();
return $items -> find ( 'LinkingMode' , 'current' );
}
2011-03-23 10:51:00 +01:00
/**
* Return a list of appropriate templates for this class , with the given suffix
*/
2012-02-27 17:43:32 +01:00
public function getTemplatesWithSuffix ( $suffix ) {
2012-02-28 00:34:15 +01:00
$templates = array ();
2011-03-23 10:51:00 +01:00
$classes = array_reverse ( ClassInfo :: ancestry ( $this -> class ));
foreach ( $classes as $class ) {
2012-02-15 13:39:45 +01:00
$template = $class . $suffix ;
if ( SSViewer :: hasTemplate ( $template )) $templates [] = $template ;
2011-03-23 10:51:00 +01:00
if ( $class == 'LeftAndMain' ) break ;
}
return $templates ;
}
2011-04-15 04:12:57 +02:00
public function Content () {
return $this -> renderWith ( $this -> getTemplatesWithSuffix ( '_Content' ));
2011-03-23 10:51:00 +01:00
}
public function getRecord ( $id ) {
$className = $this -> stat ( 'tree_class' );
2011-04-19 08:44:46 +02:00
if ( $className && $id instanceof $className ) {
2011-03-23 10:51:00 +01:00
return $id ;
2011-05-02 02:36:16 +02:00
} else if ( $id == 'root' ) {
return singleton ( $className );
2011-03-23 10:51:00 +01:00
} else if ( is_numeric ( $id )) {
return DataObject :: get_by_id ( $className , $id );
} else {
return false ;
}
}
2012-02-14 16:01:26 +01:00
/**
* @ return ArrayList
*/
public function Breadcrumbs ( $unlinked = false ) {
$title = self :: menu_title_for_class ( $this -> class );
$items = new ArrayList ( array (
new ArrayData ( array (
'Title' => $title ,
'Link' => ( $unlinked ) ? false : $this -> Link ()
))
));
$record = $this -> currentPage ();
2012-03-01 00:15:56 +01:00
if ( $record && $record -> exists ()) {
2012-02-14 16:01:26 +01:00
if ( $record -> hasExtension ( 'Hierarchy' )) {
$ancestors = $record -> getAncestors ();
2012-02-28 18:48:20 +01:00
$ancestors = new ArrayList ( array_reverse ( $ancestors -> toArray ()));
2012-02-14 16:01:26 +01:00
$ancestors -> push ( $record );
foreach ( $ancestors as $ancestor ) {
$items -> push ( new ArrayData ( array (
'Title' => $ancestor -> Title ,
'Link' => ( $unlinked ) ? false : Controller :: join_links ( $this -> Link ( 'show' ), $ancestor -> ID )
)));
}
} else {
$items -> push ( new ArrayData ( array (
'Title' => $record -> Title ,
'Link' => ( $unlinked ) ? false : Controller :: join_links ( $this -> Link ( 'show' ), $record -> ID )
)));
}
}
return $items ;
}
2011-03-23 10:51:00 +01:00
/**
* @ return String HTML
*/
public function SiteTreeAsUL () {
2012-05-15 21:28:38 +02:00
$html = $this -> getSiteTreeFor ( $this -> stat ( 'tree_class' ));
$this -> extend ( 'updateSiteTreeAsUL' , $html );
return $html ;
2011-03-23 10:51:00 +01:00
}
/**
* Get a site tree HTML listing which displays the nodes under the given criteria .
*
* @ param $className The class of the root object
* @ param $rootID The ID of the root object . If this is null then a complete tree will be
* shown
* @ param $childrenMethod The method to call to get the children of the tree . For example ,
* Children , AllChildrenIncludingDeleted , or AllHistoricalChildren
* @ return String Nested unordered list with links to each page
*/
function getSiteTreeFor ( $className , $rootID = null , $childrenMethod = null , $numChildrenMethod = null , $filterFunction = null , $minNodeCount = 30 ) {
2012-04-16 22:05:50 +02:00
// Filter criteria
$params = $this -> request -> getVar ( 'q' );
if ( isset ( $params [ 'FilterClass' ]) && $filterClass = $params [ 'FilterClass' ]){
if ( ! is_subclass_of ( $filterClass , 'CMSSiteTreeFilter' )) {
throw new Exception ( sprintf ( 'Invalid filter class passed: %s' , $filterClass ));
}
$filter = new $filterClass ( $params );
} else {
$filter = null ;
}
2011-03-23 10:51:00 +01:00
// Default childrenMethod and numChildrenMethod
2012-04-16 22:05:50 +02:00
if ( ! $childrenMethod ) $childrenMethod = ( $filter && $filter -> getChildrenMethod ()) ? $filter -> getChildrenMethod () : 'AllChildrenIncludingDeleted' ;
if ( ! $numChildrenMethod ) $numChildrenMethod = 'numChildren' ;
if ( ! $filterFunction ) $filterFunction = ( $filter ) ? array ( $filter , 'isPageIncluded' ) : null ;
2011-03-23 10:51:00 +01:00
// Get the tree root
2011-04-22 07:42:40 +02:00
$record = ( $rootID ) ? $this -> getRecord ( $rootID ) : null ;
$obj = $record ? $record : singleton ( $className );
2011-03-23 10:51:00 +01:00
// Mark the nodes of the tree to return
if ( $filterFunction ) $obj -> setMarkingFilterFunction ( $filterFunction );
$obj -> markPartialTree ( $minNodeCount , $this , $childrenMethod , $numChildrenMethod );
// Ensure current page is exposed
if ( $p = $this -> currentPage ()) $obj -> markToExpose ( $p );
// NOTE: SiteTree/CMSMain coupling :-(
2011-03-29 06:54:58 +02:00
if ( class_exists ( 'SiteTree' )) {
2012-01-23 20:29:55 +01:00
SiteTree :: prepopulate_permission_cache ( 'CanEditType' , $obj -> markedNodeIDs (), 'SiteTree::can_edit_multiple' );
2011-03-29 06:54:58 +02:00
}
2011-03-23 10:51:00 +01:00
// getChildrenAsUL is a flexible and complex way of traversing the tree
2012-04-17 22:29:45 +02:00
$controller = $this ;
$recordController = ( $this -> stat ( 'tree_class' ) == 'SiteTree' ) ? singleton ( 'CMSPageEditController' ) : $this ;
$titleFn = function ( & $child ) use ( & $controller , & $recordController ) {
$classes = $child -> CMSTreeClasses ();
if ( $controller -> isCurrentPage ( $child )) $classes .= " current " ;
2012-06-13 00:51:53 +02:00
$flags = $child -> hasMethod ( 'getStatusFlags' ) ? $child -> getStatusFlags () : false ;
2012-06-12 15:53:38 +02:00
if ( $flags ) $classes .= ' ' . implode ( ' ' , array_keys ( $flags ));
2012-05-07 14:45:25 +02:00
return " <li id= \" record- $child->ID\ " data - id = \ " $child->ID\ " data - pagetype = \ " $child->ClassName\ " class = \ " " . $classes . " \" > " .
2012-04-17 22:29:45 +02:00
" <ins class= \" jstree-icon \" > </ins> " .
2012-05-01 05:16:22 +02:00
" <a href= \" " . Controller :: join_links ( $recordController -> Link ( " show " ), $child -> ID ) . " \" title= \" " .
_t ( 'LeftAndMain.PAGETYPE' , 'Page type: ' ) .
" $child->class\ " >< ins class = \ " jstree-icon \" > </ins><span class= \" text \" > " . ( $child -> TreeTitle ) .
2012-04-17 22:29:45 +02:00
" </span></a> " ;
};
2011-03-23 10:51:00 +01:00
$html = $obj -> getChildrenAsUL (
2012-05-01 05:16:22 +02:00
" " ,
2012-04-17 22:29:45 +02:00
$titleFn ,
2012-05-01 05:16:22 +02:00
singleton ( 'CMSPagesController' ),
2011-03-23 10:51:00 +01:00
true ,
$childrenMethod ,
$numChildrenMethod ,
$minNodeCount
);
// Wrap the root if needs be.
if ( ! $rootID ) {
$rootLink = $this -> Link ( 'show' ) . '/root' ;
// This lets us override the tree title with an extension
if ( $this -> hasMethod ( 'getCMSTreeTitle' ) && $customTreeTitle = $this -> getCMSTreeTitle ()) {
$treeTitle = $customTreeTitle ;
2011-03-24 09:39:47 +01:00
} elseif ( class_exists ( 'SiteConfig' )) {
2011-03-23 10:51:00 +01:00
$siteConfig = SiteConfig :: current_site_config ();
$treeTitle = $siteConfig -> Title ;
2011-03-24 09:39:47 +01:00
} else {
$treeTitle = '...' ;
2011-03-23 10:51:00 +01:00
}
2012-03-12 10:42:07 +01:00
$html = " <ul><li id= \" record-0 \" data-id= \" 0 \" class= \" Root nodelete \" ><strong> $treeTitle </strong> "
2011-03-23 10:51:00 +01:00
. $html . " </li></ul> " ;
}
return $html ;
}
/**
* Get a subtree underneath the request param 'ID' .
* If ID = 0 , then get the whole tree .
*/
public function getsubtree ( $request ) {
$html = $this -> getSiteTreeFor (
$this -> stat ( 'tree_class' ),
$request -> getVar ( 'ID' ),
2012-04-16 22:05:50 +02:00
null ,
2011-03-23 10:51:00 +01:00
null ,
2012-04-16 22:05:50 +02:00
null ,
2011-03-23 10:51:00 +01:00
$request -> getVar ( 'minNodeCount' )
);
// Trim off the outer tag
$html = preg_replace ( '/^[\s\t\r\n]*<ul[^>]*>/' , '' , $html );
$html = preg_replace ( '/<\/ul[^>]*>[\s\t\r\n]*$/' , '' , $html );
return $html ;
}
/**
* Save handler
*/
public function save ( $data , $form ) {
$className = $this -> stat ( 'tree_class' );
// Existing or new record?
$SQL_id = Convert :: raw2sql ( $data [ 'ID' ]);
if ( substr ( $SQL_id , 0 , 3 ) != 'new' ) {
$record = DataObject :: get_by_id ( $className , $SQL_id );
if ( $record && ! $record -> canEdit ()) return Security :: permissionFailure ( $this );
2011-10-07 12:27:56 +02:00
if ( ! $record || ! $record -> ID ) throw new HTTPResponse_Exception ( " Bad record ID # " . ( int ) $data [ 'ID' ], 404 );
2011-03-23 10:51:00 +01:00
} else {
if ( ! singleton ( $this -> stat ( 'tree_class' )) -> canCreate ()) return Security :: permissionFailure ( $this );
$record = $this -> getNewItem ( $SQL_id , false );
}
// save form data into record
$form -> saveInto ( $record , true );
$record -> write ();
$this -> extend ( 'onAfterSave' , $record );
2012-03-24 03:19:02 +01:00
$this -> setCurrentPageID ( $record -> ID );
2011-03-23 10:51:00 +01:00
2012-05-18 03:17:26 +02:00
$this -> response -> addHeader ( 'X-Status' , rawurlencode ( _t ( 'LeftAndMain.SAVEDUP' , 'Saved.' )));
2012-04-19 00:11:01 +02:00
return $this -> getResponseNegotiator () -> respond ( $this -> request );
2011-03-23 10:51:00 +01:00
}
2011-04-19 11:16:56 +02:00
public function delete ( $data , $form ) {
$className = $this -> stat ( 'tree_class' );
$record = DataObject :: get_by_id ( $className , Convert :: raw2sql ( $data [ 'ID' ]));
if ( $record && ! $record -> canDelete ()) return Security :: permissionFailure ();
2011-10-07 12:27:56 +02:00
if ( ! $record || ! $record -> ID ) throw new HTTPResponse_Exception ( " Bad record ID # " . ( int ) $data [ 'ID' ], 404 );
2011-04-19 11:16:56 +02:00
$record -> delete ();
2012-03-24 03:19:02 +01:00
2012-05-18 03:17:26 +02:00
$this -> response -> addHeader ( 'X-Status' , rawurlencode ( _t ( 'LeftAndMain.DELETED' , 'Deleted.' )));
2012-03-24 03:19:02 +01:00
return $this -> getResponseNegotiator () -> respond (
2012-04-19 00:11:01 +02:00
$this -> request ,
2012-03-24 03:19:02 +01:00
array ( 'currentform' => array ( $this , 'EmptyForm' ))
);
2011-04-19 11:16:56 +02:00
}
2011-03-23 10:51:00 +01:00
/**
* Update the position and parent of a tree node .
* Only saves the node if changes were made .
*
* Required data :
* - 'ID' : The moved node
* - 'ParentID' : New parent relation of the moved node ( 0 for root )
* - 'SiblingIDs' : Array of all sibling nodes to the moved node ( incl . the node itself ) .
* In case of a 'ParentID' change , relates to the new siblings under the new parent .
*
* @ return SS_HTTPResponse JSON string with a
*/
public function savetreenode ( $request ) {
if ( ! Permission :: check ( 'SITETREE_REORGANISE' ) && ! Permission :: check ( 'ADMIN' )) {
$this -> response -> setStatusCode (
403 ,
_t ( 'LeftAndMain.CANT_REORGANISE' , " You do not have permission to rearange the site tree. Your change was not saved. " )
);
return ;
}
$className = $this -> stat ( 'tree_class' );
$statusUpdates = array ( 'modified' => array ());
$id = $request -> requestVar ( 'ID' );
$parentID = $request -> requestVar ( 'ParentID' );
2012-06-14 07:17:27 +02:00
if ( $className == 'SiteTree' && $page = DataObject :: get_by_id ( 'Page' , $id )){
$root = $page -> getParentType ();
if (( $parentID == '0' || $root == 'root' ) && ! SiteConfig :: current_site_config () -> canCreateTopLevel ()){
$this -> response -> setStatusCode (
403 ,
_t ( 'LeftAndMain.CANT_REORGANISE' , " You do not have permission to alter Top level pages. Your change was not saved. " )
);
return ;
}
}
2011-03-23 10:51:00 +01:00
$siblingIDs = $request -> requestVar ( 'SiblingIDs' );
$statusUpdates = array ( 'modified' => array ());
if ( ! is_numeric ( $id ) || ! is_numeric ( $parentID )) throw new InvalidArgumentException ();
$node = DataObject :: get_by_id ( $className , $id );
if ( $node && ! $node -> canEdit ()) return Security :: permissionFailure ( $this );
if ( ! $node ) {
$this -> response -> setStatusCode (
500 ,
_t (
'LeftAndMain.PLEASESAVE' ,
" Please Save Page: This page could not be upated because it hasn't been saved yet. "
)
);
return ;
}
// Update hierarchy (only if ParentID changed)
if ( $node -> ParentID != $parentID ) {
$node -> ParentID = ( int ) $parentID ;
$node -> write ();
$statusUpdates [ 'modified' ][ $node -> ID ] = array (
'TreeTitle' => $node -> TreeTitle
);
// Update all dependent pages
2011-03-29 06:54:58 +02:00
if ( class_exists ( 'VirtualPage' )) {
if ( $virtualPages = DataObject :: get ( " VirtualPage " , " \" CopyContentFromID \" = $node->ID " )) {
foreach ( $virtualPages as $virtualPage ) {
$statusUpdates [ 'modified' ][ $virtualPage -> ID ] = array (
'TreeTitle' => $virtualPage -> TreeTitle ()
);
}
2011-03-23 10:51:00 +01:00
}
}
2012-05-14 15:13:49 +02:00
$this -> response -> addHeader ( 'X-Status' , rawurlencode ( _t ( 'LeftAndMain.REORGANISATIONSUCCESSFUL' , 'Reorganised the site tree successfully.' )));
2011-03-23 10:51:00 +01:00
}
// Update sorting
if ( is_array ( $siblingIDs )) {
$counter = 0 ;
foreach ( $siblingIDs as $id ) {
if ( $id == $node -> ID ) {
$node -> Sort = ++ $counter ;
$node -> write ();
$statusUpdates [ 'modified' ][ $node -> ID ] = array (
'TreeTitle' => $node -> TreeTitle
);
} else if ( is_numeric ( $id )) {
// Nodes that weren't "actually moved" shouldn't be registered as
// having been edited; do a direct SQL update instead
++ $counter ;
DB :: query ( sprintf ( " UPDATE \" %s \" SET \" Sort \" = %d WHERE \" ID \" = '%d' " , $className , $counter , $id ));
}
}
2012-05-14 15:13:49 +02:00
$this -> response -> addHeader ( 'X-Status' , rawurlencode ( _t ( 'LeftAndMain.REORGANISATIONSUCCESSFUL' , 'Reorganised the site tree successfully.' )));
2011-03-23 10:51:00 +01:00
}
return Convert :: raw2json ( $statusUpdates );
}
public function CanOrganiseSitetree () {
return ! Permission :: check ( 'SITETREE_REORGANISE' ) && ! Permission :: check ( 'ADMIN' ) ? false : true ;
}
/**
* Retrieves an edit form , either for display , or to process submitted data .
* Also used in the template rendered through { @ link Right ()} in the $EditForm placeholder .
*
* This is a " pseudo-abstract " methoed , usually connected to a { @ link getEditForm ()}
* method in an entwine subclass . This method can accept a record identifier ,
* selected either in custom logic , or through { @ link currentPageID ()} .
* The form usually construct itself from { @ link DataObject -> getCMSFields ()}
* for the specific managed subclass defined in { @ link LeftAndMain :: $tree_class } .
*
* @ param HTTPRequest $request Optionally contains an identifier for the
* record to load into the form .
* @ return Form Should return a form regardless wether a record has been found .
* Form might be readonly if the current user doesn ' t have the permission to edit
* the record .
*/
/**
* @ return Form
*/
function EditForm ( $request = null ) {
return $this -> getEditForm ();
}
2011-04-15 05:32:43 +02:00
/**
* Calls { @ link SiteTree -> getCMSFields ()}
*
* @ param Int $id
2011-05-11 09:51:54 +02:00
* @ param FieldList $fields
2011-04-15 05:32:43 +02:00
* @ return Form
*/
public function getEditForm ( $id = null , $fields = null ) {
2011-03-23 10:51:00 +01:00
if ( ! $id ) $id = $this -> currentPageID ();
if ( is_object ( $id )) {
$record = $id ;
} else {
2011-05-02 02:36:16 +02:00
$record = $this -> getRecord ( $id );
2011-03-23 10:51:00 +01:00
if ( $record && ! $record -> canView ()) return Security :: permissionFailure ( $this );
}
if ( $record ) {
2011-04-15 05:32:43 +02:00
$fields = ( $fields ) ? $fields : $record -> getCMSFields ();
2011-03-23 10:51:00 +01:00
if ( $fields == null ) {
user_error (
2011-10-28 03:37:27 +02:00
" getCMSFields() returned null - it should return a FieldList object.
2011-03-23 10:51:00 +01:00
Perhaps you forgot to put a return statement at the end of your method ? " ,
E_USER_ERROR
);
}
// Add hidden fields which are required for saving the record
// and loading the UI state
if ( ! $fields -> dataFieldByName ( 'ClassName' )) {
$fields -> push ( new HiddenField ( 'ClassName' ));
}
if (
Object :: has_extension ( $this -> stat ( 'tree_class' ), 'Hierarchy' )
&& ! $fields -> dataFieldByName ( 'ParentID' )
) {
$fields -> push ( new HiddenField ( 'ParentID' ));
}
2011-07-21 20:14:33 +02:00
// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
if ( in_array ( 'CMSPreviewable' , class_implements ( $record ))) {
$navField = new LiteralField ( 'SilverStripeNavigator' , $this -> getSilverStripeNavigator ());
$navField -> setAllowHTML ( true );
$fields -> push ( $navField );
}
2011-03-23 10:51:00 +01:00
if ( $record -> hasMethod ( 'getAllCMSActions' )) {
$actions = $record -> getAllCMSActions ();
} else {
$actions = $record -> getCMSActions ();
// add default actions if none are defined
if ( ! $actions || ! $actions -> Count ()) {
2011-04-19 08:44:46 +02:00
if ( $record -> hasMethod ( 'canEdit' ) && $record -> canEdit ()) {
2012-02-17 00:35:10 +01:00
$actions -> push (
FormAction :: create ( 'save' , _t ( 'CMSMain.SAVE' , 'Save' ))
-> addExtraClass ( 'ss-ui-action-constructive' ) -> setAttribute ( 'data-icon' , 'accept' )
);
2011-03-23 10:51:00 +01:00
}
2012-03-07 00:36:47 +01:00
if ( $record -> hasMethod ( 'canDelete' ) && $record -> canDelete ()) {
$actions -> push (
FormAction :: create ( 'delete' , _t ( 'ModelAdmin.DELETE' , 'Delete' ))
-> addExtraClass ( 'ss-ui-action-destructive' )
);
}
2011-03-23 10:51:00 +01:00
}
}
2012-02-17 00:35:10 +01:00
// Use <button> to allow full jQuery UI styling
$actionsFlattened = $actions -> dataFields ();
if ( $actionsFlattened ) foreach ( $actionsFlattened as $action ) $action -> setUseButtonTag ( true );
2011-03-23 10:51:00 +01:00
$form = new Form ( $this , " EditForm " , $fields , $actions );
2011-03-31 10:52:29 +02:00
$form -> addExtraClass ( 'cms-edit-form' );
2011-03-23 10:51:00 +01:00
$form -> loadDataFrom ( $record );
2011-10-29 21:56:33 +02:00
$form -> setTemplate ( $this -> getTemplatesWithSuffix ( '_EditForm' ));
2012-04-18 22:11:40 +02:00
$form -> setAttribute ( 'data-pjax-fragment' , 'CurrentForm' );
2011-03-23 10:51:00 +01:00
2011-10-29 21:59:21 +02:00
// Set this if you want to split up tabs into a separate header row
// if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
2011-03-23 10:51:00 +01:00
// Add a default or custom validator.
// @todo Currently the default Validator.js implementation
// adds javascript to the document body, meaning it won't
// be included properly if the associated fields are loaded
// through ajax. This means only serverside validation
// will kick in for pages+validation loaded through ajax.
// This will be solved by using less obtrusive javascript validation
// in the future, see http://open.silverstripe.com/ticket/2915 and
// http://open.silverstripe.com/ticket/3386
if ( $record -> hasMethod ( 'getCMSValidator' )) {
$validator = $record -> getCMSValidator ();
// The clientside (mainly LeftAndMain*.js) rely on ajax responses
// which can be evaluated as javascript, hence we need
// to override any global changes to the validation handler.
$form -> setValidator ( $validator );
} else {
$form -> unsetValidator ();
}
2011-04-19 08:44:46 +02:00
if ( $record -> hasMethod ( 'canEdit' ) && ! $record -> canEdit ()) {
2011-03-23 10:51:00 +01:00
$readonlyFields = $form -> Fields () -> makeReadonly ();
$form -> setFields ( $readonlyFields );
}
} else {
2012-03-08 18:20:11 +01:00
$form = $this -> EmptyForm ();
2011-03-23 10:51:00 +01:00
}
return $form ;
}
/**
* Returns a placeholder form , used by { @ link getEditForm ()} if no record is selected .
* Our javascript logic always requires a form to be present in the CMS interface .
*
* @ return Form
*/
function EmptyForm () {
$form = new Form (
$this ,
" EditForm " ,
2011-05-11 09:51:54 +02:00
new FieldList (
2011-04-24 01:30:28 +02:00
// new HeaderField(
// 'WelcomeHeader',
// $this->getApplicationName()
// ),
// new LiteralField(
// 'WelcomeText',
// sprintf('<p id="WelcomeMessage">%s %s. %s</p>',
// _t('LeftAndMain_right.ss.WELCOMETO','Welcome to'),
// $this->getApplicationName(),
// _t('CHOOSEPAGE','Please choose an item from the left.')
// )
// )
2011-03-23 10:51:00 +01:00
),
2011-05-11 09:51:54 +02:00
new FieldList ()
2011-03-23 10:51:00 +01:00
);
$form -> unsetValidator ();
2011-03-31 10:52:29 +02:00
$form -> addExtraClass ( 'cms-edit-form' );
$form -> addExtraClass ( 'root-form' );
2011-04-15 05:32:43 +02:00
$form -> setTemplate ( $this -> getTemplatesWithSuffix ( '_EditForm' ));
2012-04-18 22:11:40 +02:00
$form -> setAttribute ( 'data-pjax-fragment' , 'CurrentForm' );
2011-03-23 10:51:00 +01:00
return $form ;
}
2012-01-03 18:09:40 +01:00
/**
* Return the CMS ' s HTML - editor toolbar
*/
public function EditorToolbar () {
2012-04-04 16:59:30 +02:00
return HtmlEditorField_Toolbar :: create ( $this , " EditorToolbar " );
2012-01-03 18:09:40 +01:00
}
2012-02-15 13:39:45 +01:00
/**
* Renders a panel containing tools which apply to all displayed
* " content " ( mostly through { @ link EditForm ()}), for example a tree navigation or a filter panel .
* Auto - detects applicable templates by naming convention : " <controller classname>_Tools.ss " ,
* and takes the most specific template ( see { @ link getTemplatesWithSuffix ()}) .
* To explicitly disable the panel in the subclass , simply create a more specific , empty template .
*
* @ return String HTML
*/
public function Tools () {
$templates = $this -> getTemplatesWithSuffix ( '_Tools' );
if ( $templates ) {
$viewer = new SSViewer ( $templates );
return $viewer -> process ( $this );
} else {
return false ;
}
}
/**
* Renders a panel containing tools which apply to the currently displayed edit form .
* The main difference to { @ link Tools ()} is that the panel is displayed within
* the element structure of the form panel ( rendered through { @ link EditForm }) .
* This means the panel will be loaded alongside new forms , and refreshed upon save ,
* which can mean a performance hit , depending on how complex your panel logic gets .
* Any form fields contained in the returned markup will also be submitted with the main form ,
* which might be desired depending on the implementation details .
*
* @ return String HTML
*/
public function EditFormTools () {
$templates = $this -> getTemplatesWithSuffix ( '_EditFormTools' );
if ( $templates ) {
$viewer = new SSViewer ( $templates );
return $viewer -> process ( $this );
} else {
return false ;
}
}
2011-03-23 10:51:00 +01:00
/**
* Batch Actions Handler
*/
function batchactions () {
return new CMSBatchActionHandler ( $this , 'batchactions' , $this -> stat ( 'tree_class' ));
}
/**
* @ return Form
*/
function BatchActionsForm () {
$actions = $this -> batchactions () -> batchActionList ();
2011-04-22 13:34:01 +02:00
$actionsMap = array ( '-1' => _t ( 'LeftAndMain.DropdownBatchActionsDefault' , 'Actions' ));
2011-03-23 10:51:00 +01:00
foreach ( $actions as $action ) $actionsMap [ $action -> Link ] = $action -> Title ;
$form = new Form (
$this ,
'BatchActionsForm' ,
2011-05-11 09:51:54 +02:00
new FieldList (
2011-03-23 10:51:00 +01:00
new HiddenField ( 'csvIDs' ),
2012-04-04 16:59:30 +02:00
DropdownField :: create (
2011-03-23 10:51:00 +01:00
'Action' ,
false ,
$actionsMap
2012-03-08 01:33:15 +01:00
) -> setAttribute ( 'autocomplete' , 'off' )
2011-03-23 10:51:00 +01:00
),
2011-05-11 09:51:54 +02:00
new FieldList (
2011-03-23 10:51:00 +01:00
// TODO i18n
new FormAction ( 'submit' , " Go " )
)
);
2011-04-24 01:05:59 +02:00
$form -> addExtraClass ( 'cms-batch-actions nostyle' );
2011-03-23 10:51:00 +01:00
$form -> unsetValidator ();
return $form ;
}
public function printable () {
$form = $this -> getEditForm ( $this -> currentPageID ());
if ( ! $form ) return false ;
$form -> transform ( new PrintableTransformation ());
$form -> setActions ( null );
Requirements :: clear ();
2012-03-24 04:38:57 +01:00
Requirements :: css ( FRAMEWORK_ADMIN_DIR . '/css/LeftAndMain_printable.css' );
2011-03-23 10:51:00 +01:00
return array (
" PrintForm " => $form
);
}
2011-07-21 20:14:33 +02:00
/**
* Used for preview controls , mainly links which switch between different states of the page .
*
* @ return ArrayData
*/
function getSilverStripeNavigator () {
$page = $this -> currentPage ();
if ( $page ) {
$navigator = new SilverStripeNavigator ( $page );
return $navigator -> renderWith ( $this -> getTemplatesWithSuffix ( '_SilverStripeNavigator' ));
} else {
return false ;
}
}
2011-03-23 10:51:00 +01:00
/**
* Identifier for the currently shown record ,
* in most cases a database ID . Inspects the following
* sources ( in this order ) :
* - GET / POST parameter named 'ID'
* - URL parameter named 'ID'
* - Session value namespaced by classname , e . g . " CMSMain.currentPage "
*
* @ return int
*/
public function currentPageID () {
2012-06-22 17:47:45 +02:00
if ( $this -> request -> requestVar ( 'ID' ) && is_numeric ( $this -> request -> requestVar ( 'ID' ))) {
2011-03-23 10:51:00 +01:00
return $this -> request -> requestVar ( 'ID' );
2012-02-28 18:48:02 +01:00
} elseif ( isset ( $this -> urlParams [ 'ID' ]) && is_numeric ( $this -> urlParams [ 'ID' ])) {
return $this -> urlParams [ 'ID' ];
2011-03-23 10:51:00 +01:00
} elseif ( Session :: get ( " { $this -> class } .currentPage " )) {
return Session :: get ( " { $this -> class } .currentPage " );
} else {
return null ;
}
}
/**
* Forces the current page to be set in session ,
* which can be retrieved later through { @ link currentPageID ()} .
* Keep in mind that setting an ID through GET / POST or
* as a URL parameter will overrule this value .
*
* @ param int $id
*/
public function setCurrentPageID ( $id ) {
Session :: set ( " { $this -> class } .currentPage " , $id );
}
/**
* Uses { @ link getRecord ()} and { @ link currentPageID ()}
* to get the currently selected record .
*
* @ return DataObject
*/
public function currentPage () {
return $this -> getRecord ( $this -> currentPageID ());
}
/**
* Compares a given record to the currently selected one ( if any ) .
* Used for marking the current tree node .
*
* @ return boolean
*/
public function isCurrentPage ( DataObject $record ) {
return ( $record -> ID == $this -> currentPageID ());
}
/**
2011-05-15 05:43:21 +02:00
* URL to a previewable record which is shown through this controller .
* The controller might not have any previewable content , in which case
* this method returns FALSE .
*
* @ return String | boolean
2011-03-23 10:51:00 +01:00
*/
2012-04-18 22:59:18 +02:00
public function LinkPreview () {
2011-10-29 19:12:33 +02:00
return false ;
2011-05-15 05:43:21 +02:00
}
2011-03-23 10:51:00 +01:00
/**
* Return the version number of this application .
* Uses the subversion path information in < mymodule >/ silverstripe_version
2012-02-01 18:30:34 +01:00
* ( automacially replaced by build scripts ) .
2011-03-23 10:51:00 +01:00
*
* @ return string
*/
public function CMSVersion () {
2012-04-15 05:00:52 +02:00
$frameworkVersion = file_get_contents ( FRAMEWORK_PATH . '/silverstripe_version' );
if ( ! $frameworkVersion ) $frameworkVersion = _t ( 'LeftAndMain.VersionUnknown' , 'Unknown' );
2012-02-01 18:30:34 +01:00
return sprintf (
2012-04-15 05:00:52 +02:00
" Framework: %s " ,
$frameworkVersion
2012-02-01 18:30:34 +01:00
);
2011-03-23 10:51:00 +01:00
}
/**
* @ return array
*/
function SwitchView () {
if ( $page = $this -> currentPage ()) {
$nav = SilverStripeNavigator :: get_for_record ( $page );
return $nav [ 'items' ];
}
}
2011-04-15 04:12:57 +02:00
/**
* @ return SiteConfig
*/
function SiteConfig () {
2011-05-01 12:20:30 +02:00
return ( class_exists ( 'SiteConfig' )) ? SiteConfig :: current_site_config () : null ;
2011-04-15 04:12:57 +02:00
}
2011-03-23 10:51:00 +01:00
/**
* The application name . Customisable by calling
* LeftAndMain :: setApplicationName () - the first parameter .
*
* @ var String
*/
2012-04-13 16:21:30 +02:00
static $application_name = 'SilverStripe' ;
2011-03-23 10:51:00 +01:00
/**
* @ param String $name
*/
2011-03-31 01:06:46 +02:00
static function setApplicationName ( $name ) {
2011-03-23 10:51:00 +01:00
self :: $application_name = $name ;
}
/**
* Get the application name .
2011-08-22 06:44:41 +02:00
*
* @ return string
2011-03-23 10:51:00 +01:00
*/
function getApplicationName () {
return self :: $application_name ;
}
2011-06-09 05:47:59 +02:00
/**
2011-08-22 06:44:41 +02:00
* @ return string
2011-06-09 05:47:59 +02:00
*/
function Title () {
2011-08-22 06:44:41 +02:00
$app = $this -> getApplicationName ();
return ( $section = $this -> SectionTitle ()) ? sprintf ( '%s - %s' , $app , $section ) : $app ;
2011-06-09 05:47:59 +02:00
}
2011-03-23 10:51:00 +01:00
/**
2011-08-22 06:44:41 +02:00
* Return the title of the current section . Either this is pulled from
* the current panel ' s menu_title or from the first active menu
*
* @ return string
2011-03-23 10:51:00 +01:00
*/
function SectionTitle () {
2011-08-22 06:44:41 +02:00
if ( $title = $this -> stat ( 'menu_title' )) return $title ;
2012-05-03 13:49:19 +02:00
foreach ( $this -> MainMenu () as $menuItem ) {
2011-08-22 06:44:41 +02:00
if ( $menuItem -> LinkingMode != 'link' ) return $menuItem -> Title ;
2011-03-23 10:51:00 +01:00
}
}
/**
* Return the base directory of the tiny_mce codebase
*/
function MceRoot () {
return MCE_ROOT ;
}
2011-07-04 16:36:24 +02:00
/**
* Same as { @ link ViewableData -> CSSClasses ()}, but with a changed name
* to avoid problems when using { @ link ViewableData -> customise ()}
* ( which always returns " ArrayData " from the $original object ) .
*
* @ return String
*/
function BaseCSSClasses () {
2012-03-12 14:05:54 +01:00
return $this -> CSSClasses ( 'Controller' );
2011-07-04 16:36:24 +02:00
}
2011-05-15 05:43:21 +02:00
function IsPreviewExpanded () {
return ( $this -> request -> getVar ( 'cms-preview-expanded' ));
}
2011-12-18 16:57:26 +01:00
/**
* @ return String
*/
function Locale () {
2012-03-27 06:57:42 +02:00
return DBField :: create_field ( 'DBLocale' , i18n :: get_locale ());
2011-12-18 16:57:26 +01:00
}
2012-03-05 16:07:20 +01:00
function providePermissions () {
$perms = array (
" CMS_ACCESS_LeftAndMain " => array (
'name' => _t ( 'CMSMain.ACCESSALLINTERFACES' , 'Access to all CMS sections' ),
'category' => _t ( 'Permission.CMS_ACCESS_CATEGORY' , 'CMS Access' ),
'help' => _t ( 'CMSMain.ACCESSALLINTERFACESHELP' , 'Overrules more specific access settings.' ),
'sort' => - 100
)
);
// Add any custom ModelAdmin subclasses. Can't put this on ModelAdmin itself
// since its marked abstract, and needs to be singleton instanciated.
foreach ( ClassInfo :: subclassesFor ( 'ModelAdmin' ) as $i => $class ) {
if ( $class == 'ModelAdmin' ) continue ;
if ( ClassInfo :: classImplements ( $class , 'TestOnly' )) continue ;
$title = _t ( " { $class } .MENUTITLE " , LeftAndMain :: menu_title_for_class ( $class ));
$perms [ " CMS_ACCESS_ " . $class ] = array (
2012-05-01 21:44:54 +02:00
'name' => _t (
2012-03-05 16:07:20 +01:00
'CMSMain.ACCESS' ,
2012-05-01 21:44:54 +02:00
" Access to ' { title}' section " ,
" Item in permission selection identifying the admin section. Example: Access to 'Files & Images' " ,
array ( 'title' => $title )
),
2012-03-05 16:07:20 +01:00
'category' => _t ( 'Permission.CMS_ACCESS_CATEGORY' , 'CMS Access' )
);
}
return $perms ;
}
2011-05-15 05:43:21 +02:00
2011-03-23 10:51:00 +01:00
/**
* Register the given javascript file as required in the CMS .
2012-03-24 04:38:57 +01:00
* Filenames should be relative to the base , eg , FRAMEWORK_DIR . '/javascript/loader.js'
2011-03-23 10:51:00 +01:00
*/
public static function require_javascript ( $file ) {
self :: $extra_requirements [ 'javascript' ][] = array ( $file );
}
/**
* Register the given stylesheet file as required .
*
* @ param $file String Filenames should be relative to the base , eg , THIRDPARTY_DIR . '/tree/tree.css'
* @ param $media String Comma - separated list of media - types ( e . g . " screen,projector " )
* @ see http :// www . w3 . org / TR / REC - CSS2 / media . html
*/
public static function require_css ( $file , $media = null ) {
self :: $extra_requirements [ 'css' ][] = array ( $file , $media );
}
/**
* Register the given " themeable stylesheet " as required .
* Themeable stylesheets have globally unique names , just like templates and PHP files .
* Because of this , they can be replaced by similarly named CSS files in the theme directory .
*
* @ param $name String The identifier of the file . For example , css / MyFile . css would have the identifier " MyFile "
* @ param $media String Comma - separated list of media - types ( e . g . " screen,projector " )
*/
static function require_themed_css ( $name , $media = null ) {
self :: $extra_requirements [ 'themedcss' ][] = array ( $name , $media );
}
}
/**
* @ package cms
* @ subpackage core
*/
class LeftAndMainMarkingFilter {
/**
* @ var array Request params ( unsanitized )
*/
protected $params = array ();
/**
* @ param array $params Request params ( unsanitized )
*/
function __construct ( $params = null ) {
$this -> ids = array ();
$this -> expanded = array ();
$parents = array ();
$q = $this -> getQuery ( $params );
$res = $q -> execute ();
if ( ! $res ) return ;
// And keep a record of parents we don't need to get parents
// of themselves, as well as IDs to mark
foreach ( $res as $row ) {
if ( $row [ 'ParentID' ]) $parents [ $row [ 'ParentID' ]] = true ;
$this -> ids [ $row [ 'ID' ]] = true ;
}
// We need to recurse up the tree,
// finding ParentIDs for each ID until we run out of parents
while ( ! empty ( $parents )) {
$res = DB :: query ( 'SELECT "ParentID", "ID" FROM "SiteTree" WHERE "ID" in (' . implode ( ',' , array_keys ( $parents )) . ')' );
$parents = array ();
foreach ( $res as $row ) {
if ( $row [ 'ParentID' ]) $parents [ $row [ 'ParentID' ]] = true ;
$this -> ids [ $row [ 'ID' ]] = true ;
$this -> expanded [ $row [ 'ID' ]] = true ;
}
}
}
protected function getQuery ( $params ) {
$where = array ();
$SQL_params = Convert :: raw2sql ( $params );
if ( isset ( $SQL_params [ 'ID' ])) unset ( $SQL_params [ 'ID' ]);
foreach ( $SQL_params as $name => $val ) {
switch ( $name ) {
default :
// Partial string match against a variety of fields
if ( ! empty ( $val ) && singleton ( " SiteTree " ) -> hasDatabaseField ( $name )) {
$where [] = " \" $name\ " LIKE '%$val%' " ;
}
}
}
return new SQLQuery (
array ( " ParentID " , " ID " ),
'SiteTree' ,
$where
);
}
function mark ( $node ) {
$id = $node -> ID ;
if ( array_key_exists (( int ) $id , $this -> expanded )) $node -> markOpened ();
return array_key_exists (( int ) $id , $this -> ids ) ? $this -> ids [ $id ] : false ;
}
}
2012-02-12 21:22:11 +01:00