API Refactor to always redirect to accessible Admin location.

Tries to find an accessible section in the current site, falls back to
searching across all sites and all sections.

Also adds more powerful and generic functionss:
Subsites::all_sites - get the full list
Subsites::all_accessible_sites - get Member accessible list
LeftAndMainExtension::sectionSites - get section-specific list
This commit is contained in:
Mateusz Uzdowski 2013-10-16 13:25:06 +13:00
parent 91cca0c64d
commit 5b00ba352f
3 changed files with 193 additions and 102 deletions

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* Decorator designed to add subsites support to LeftAndMain * Decorator designed to add subsites support to LeftAndMain
* *
* @package subsites * @package subsites
*/ */
class LeftAndMainSubsites extends Extension { class LeftAndMainSubsites extends Extension {
@ -9,26 +9,9 @@ class LeftAndMainSubsites extends Extension {
private static $allowed_actions = array('CopyToSubsite'); private static $allowed_actions = array('CopyToSubsite');
function init() { function init() {
//Use the session variable for current subsite in the CMS only
Subsite::$use_session_subsiteid = true;
Requirements::css('subsites/css/LeftAndMain_Subsites.css'); Requirements::css('subsites/css/LeftAndMain_Subsites.css');
Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js'); Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js');
Requirements::javascript('subsites/javascript/VirtualPage_Subsites.js'); Requirements::javascript('subsites/javascript/VirtualPage_Subsites.js');
if(isset($_GET['SubsiteID'])) {
// Clear current page when subsite changes (or is set for the first time)
if(!Session::get('SubsiteID') || $_GET['SubsiteID'] != Session::get('SubsiteID')) {
Session::clear("{$this->owner->class}.currentPage");
}
// Update current subsite in session
Subsite::changeSubsite($_GET['SubsiteID']);
//Redirect to clear the current page
return $this->owner->redirect('admin/');
}
// Set subsite ID based on currently shown record // Set subsite ID based on currently shown record
$req = $this->owner->getRequest(); $req = $this->owner->getRequest();
@ -50,24 +33,61 @@ class LeftAndMainSubsites extends Extension {
function updatePageOptions(&$fields) { function updatePageOptions(&$fields) {
$fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID())); $fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID()));
} }
/* /**
* Returns a list of the subsites accessible to the current user * Find all subsites accessible for current user on this controller.
*
* @return ArrayList of {@link Subsite} instances.
*/ */
public function Subsites() { function sectionSites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
// figure out what permission the controller needs // Rationalise member arguments
// Subsite::accessible_sites() expects something, so if there's no permission if(!$member) $member = Member::currentUser();
// then fallback to using CMS_ACCESS_LeftAndMain. if(!$member) return new ArrayList();
$permission = 'CMS_ACCESS_' . $this->owner->class; if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
$available = Permission::get_codes(false);
if(!isset($available[$permission])) { // Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires
$permission = $this->owner->stat('required_permission_codes'); // us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView.
if(!$permission) { $codes = array();
$permission = 'CMS_ACCESS_LeftAndMain'; $extraCodes = Config::inst()->get($this->owner->class, 'required_permission_codes');
if($extraCodes !== false) {
if($extraCodes) $codes = array_merge($codes, (array)$extraCodes);
else $codes[] = "CMS_ACCESS_{$this->owner->class}";
} else {
// Check overriden - all subsites accessible.
return Subsite::all_sites();
}
// Find subsites satisfying all permissions for the Member.
$codesPerSite = array();
$sitesArray = array();
foreach ($codes as $code) {
$sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member);
foreach ($sites as $site) {
// Build the structure for checking how many codes match.
$codesPerSite[$site->ID][$code] = true;
// Retain Subsite objects for later.
$sitesArray[$site->ID] = $site;
} }
} }
return Subsite::accessible_sites($permission); // Find sites that satisfy all codes conjuncitvely.
$accessibleSites = new ArrayList();
foreach ($codesPerSite as $siteID => $siteCodes) {
if (count($siteCodes)==count($codes)) {
$accessibleSites->push($sitesArray[$siteID]);
}
}
return $accessibleSites;
}
/*
* Returns a list of the subsites accessible to the current user.
* It's enough for any section to be accessible for the section to be included.
*/
public function Subsites() {
return Subsite::all_accessible_sites();
} }
/* /*
@ -101,38 +121,26 @@ class LeftAndMainSubsites extends Extension {
return $output; return $output;
} }
/* public function alternateMenuDisplayCheck($controllerName) {
* Returns a subset of the main menu, filtered by admins that have if(!class_exists($controllerName)){
* a subsiteCMSShowInMenu method returning true return false;
* }
* @return ArrayList
*/ // Check subsite support.
public function SubsiteMainMenu(){
if(Subsite::currentSubsiteID() == 0){ if(Subsite::currentSubsiteID() == 0){
return $this->owner->MainMenu(); // Main site always supports everything.
} return true;
// loop main menu items, add all items that have subsite support } else {
$mainMenu = $this->owner->MainMenu(); $controller = singleton($controllerName);
$subsitesMenu = new ArrayList(); if($controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu()){
return true;
foreach($mainMenu as $menuItem){
$controllerName = $menuItem->MenuItem->controller;
if(class_exists($controllerName)){
$controller = singleton($controllerName);
if($controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu()){
$subsitesMenu->push($menuItem);
}
} }
if($menuItem->Code == 'Help'){
$subsitesMenu->push($menuItem);
}
} }
return $subsitesMenu;
// It's not necessary to check access permissions here. Framework calls canView on the controller,
// which in turn uses the Permission API which is augmented by our GroupSubsites.
return false;
} }
public function CanAddSubsites() { public function CanAddSubsites() {
@ -140,58 +148,90 @@ class LeftAndMainSubsites extends Extension {
} }
/** /**
* Alternative security checker for LeftAndMain. * Do some pre-flight checks if a subsite switch is needed.
* If security isn't found, then it will switch to a subsite where we do have access. * We redirect the user to something accessible if the current section/subsite is forbidden.
*/ */
public function alternateAccessCheck() { public function onBeforeInit() {
// We are accessing the CMS, so we need to let Subsites know we will be using the session.
Subsite::$use_session_subsiteid = true;
// Do not try to be smart for AJAX requests.
if ($this->owner->request->isAjax()) {
return;
}
// Catch forced subsite changes that need to cause CMS reloads.
if(isset($_GET['SubsiteID'])) {
// Clear current page when subsite changes (or is set for the first time)
if(!Session::get('SubsiteID') || $_GET['SubsiteID'] != Session::get('SubsiteID')) {
Session::clear("{$this->owner->class}.currentPage");
}
// Update current subsite in session
Subsite::changeSubsite($_GET['SubsiteID']);
//Redirect to clear the current page
return $this->owner->redirect('admin/');
}
$className = $this->owner->class; $className = $this->owner->class;
// Switch to the subsite of the current page // Transparently switch to the subsite of the current page.
if ($this->owner->class == 'CMSMain' && $currentPage = $this->owner->currentPage()) { if ($this->owner->class == 'CMSMain' && $currentPage = $this->owner->currentPage()) {
if (Subsite::currentSubsiteID() != $currentPage->SubsiteID) { if (Subsite::currentSubsiteID() != $currentPage->SubsiteID) {
Subsite::changeSubsite($currentPage->SubsiteID); Subsite::changeSubsite($currentPage->SubsiteID);
} }
} }
// Switch to a subsite that this user can actually access.
$member = Member::currentUser();
if($member && Permission::checkMember($member, 'ADMIN')) return true; // admin can access all subsites
$sites = Subsite::accessible_sites("CMS_ACCESS_{$this->owner->class}", true)->map('ID', 'Title');
if(is_object($sites)) $sites = $sites->toArray();
if($sites && !isset($sites[Subsite::currentSubsiteID()])) { // If we can view current URL there is nothing to do.
$siteIDs = array_keys($sites); if ($this->owner->canView()) {
Subsite::changeSubsite($siteIDs[0]); return;
return true;
} }
// Switch to a different top-level menu item // Admin can access everything, no point in checking.
$member = Member::currentUser();
if($member && Permission::checkMember($member, 'ADMIN')) return;
// Check if we have access to current section on the current subsite.
$accessibleSites = $this->owner->sectionSites($member);
if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) {
// Current section can be accessed on the current site, all good.
return;
}
// If the current section is not accessible, try at least to stick to the same subsite.
$menu = CMSMenu::get_menu_items(); $menu = CMSMenu::get_menu_items();
foreach($menu as $candidate) { foreach($menu as $candidate) {
if($candidate->controller != $this->owner->class) { if($candidate->controller && $candidate->controller!=$this->owner->class) {
$sites = Subsite::accessible_sites("CMS_ACCESS_{$candidate->controller}", true)->map('ID', 'Title');
if(is_object($sites)) $sites = $sites->toArray(); $accessibleSites = singleton($candidate->controller)->sectionSites($member);
if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) {
if($sites && !isset($sites[Subsite::currentSubsiteID()])) { // Section is accessible, redirect there.
$siteIDs = array_keys($sites); $this->owner->redirect(singleton($candidate->controller)->Link());
Subsite::changeSubsite($siteIDs[0]); return;
$cClass = $candidate->controller;
$cObj = new $cClass();
$this->owner->redirect($cObj->Link());
return null;
} }
} }
} }
// If all of those fail, you really don't have access to the CMS // Finally, if no section is available, move to any other permitted subsite.
return null; foreach($menu as $candidate) {
if($candidate->controller && $candidate->controller != $this->owner->class) {
$accessibleSites = singleton($candidate->controller)->sectionSites($member);
if ($accessibleSites->count()) {
Subsite::changeSubsite($accessibleSites->First()->ID);
$this->owner->redirect(singleton($candidate->controller)->Link());
return;
}
}
}
} }
function augmentNewSiteTreeItem(&$item) { function augmentNewSiteTreeItem(&$item) {
$item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID(); $item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID();
} }
function onAfterSave($record) { function onAfterSave($record) {
if($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) { if($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) {
$this->owner->response->addHeader('X-Status', rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.'))); $this->owner->response->addHeader('X-Status', rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.')));

View File

@ -310,10 +310,9 @@ JS;
if(isset($_GET['SubsiteID'])) { if(isset($_GET['SubsiteID'])) {
$id = (int)$_GET['SubsiteID']; $id = (int)$_GET['SubsiteID'];
} } else if (Subsite::$use_session_subsiteid) {
else if (Subsite::$use_session_subsiteid) {
$id = Session::get('SubsiteID'); $id = Session::get('SubsiteID');
} }
if($id === NULL) { if($id === NULL) {
$id = self::getSubsiteIDForDomain(); $id = self::getSubsiteIDForDomain();
@ -522,12 +521,64 @@ JS;
return $duplicate; return $duplicate;
} }
/**
* Return all subsites, regardless of permissions (augmented with main site).
*
* @return SS_List List of {@link Subsite} objects (DataList or ArrayList).
*/
public static function all_sites($includeMainSite = true, $mainSiteTitle = "Main site") {
$subsites = Subsite::get();
if($includeMainSite) {
$subsites = $subsites->toArray();
$mainSite = new Subsite();
$mainSite->Title = $mainSiteTitle;
array_unshift($subsites, $mainSite);
$subsites = ArrayList::create($subsites);
}
return $subsites;
}
/*
* Returns an ArrayList of the subsites accessible to the current user.
* It's enough for any section to be accessible for the site to be included.
*
* @return ArrayList of {@link Subsite} instances.
*/
public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) {
// Rationalise member arguments
if(!$member) $member = Member::currentUser();
if(!$member) return new ArrayList();
if(!is_object($member)) $member = DataObject::get_by_id('Member', $member);
$subsites = new ArrayList();
// Collect subsites for all sections.
$menu = CMSMenu::get_viewable_menu_items();
foreach($menu as $candidate) {
if ($candidate->controller) {
$accessibleSites = singleton($candidate->controller)->sectionSites(
$includeMainSite,
$mainSiteTitle,
$member
);
// Replace existing keys so no one site appears twice.
$subsites->merge($accessibleSites);
}
}
$subsites->removeDuplicates();
return $subsites;
}
/** /**
* Return the subsites that the current user can access. * Return the subsites that the current user can access by given permission.
* Look for one of the given permission codes on the site. * Sites will only be included if they have a Title.
*
* Sites will only be included if they have a Title
* *
* @param $permCode array|string Either a single permission code or an array of permission codes. * @param $permCode array|string Either a single permission code or an array of permission codes.
* @param $includeMainSite If true, the main site will be included if appropriate. * @param $includeMainSite If true, the main site will be included if appropriate.

View File

@ -25,7 +25,7 @@
<div class="cms-panel-content center"> <div class="cms-panel-content center">
<ul class="cms-menu-list"> <ul class="cms-menu-list">
<% loop $SubsiteMainMenu %> <% loop $MainMenu %>
<li class="$LinkingMode $FirstLast <% if $LinkingMode == 'link' %><% else %>opened<% end_if %>" id="Menu-$Code" title="$Title.ATT"> <li class="$LinkingMode $FirstLast <% if $LinkingMode == 'link' %><% else %>opened<% end_if %>" id="Menu-$Code" title="$Title.ATT">
<a href="$Link" <% if $Code == 'Help' %>target="_blank"<% end_if %>> <a href="$Link" <% if $Code == 'Help' %>target="_blank"<% end_if %>>
<span class="icon icon-16 icon-{$Code.LowerCase}">&nbsp;</span> <span class="icon icon-16 icon-{$Code.LowerCase}">&nbsp;</span>