silverstripe-cms/code/Controllers/SilverStripeNavigator.php
2016-08-10 16:09:23 +12:00

417 lines
12 KiB
PHP

<?php
namespace SilverStripe\CMS\Controllers;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\Versioning\Versioned;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\Security\Member;
use ViewableData;
use CMSPreviewable;
use ClassInfo;
use LeftAndMain;
use Controller;
use SiteTreeFutureState;
use SilverStripe\CMS\Model\RedirectorPage;
/**
* Utility class representing links to different views of a record
* for CMS authors, usually for {@link SiteTree} objects with "stage" and "live" links.
* Useful both in the CMS and alongside the page template (for logged in authors).
* The class can be used for any {@link DataObject} subclass implementing the {@link CMSPreviewable} interface.
*
* New item types can be defined by extending the {@link SilverStripeNavigatorItem} class,
* for example the "cmsworkflow" module defines a new "future state" item with a date selector
* to view embargoed data at a future point in time. So the item doesn't always have to be a simple link.
*
* @package cms
* @subpackage content
*/
class SilverStripeNavigator extends ViewableData {
/**
* @var DataObject|CMSPreviewable
*/
protected $record;
/**
* @param DataObject|CMSPreviewable $record
*/
public function __construct(CMSPreviewable $record) {
parent::__construct();
$this->record = $record;
}
/**
* @return SS_List of SilverStripeNavigatorItem
*/
public function getItems() {
$items = array();
$classes = ClassInfo::subclassesFor('SilverStripe\\CMS\\Controllers\\SilverStripeNavigatorItem');
unset($classes['SilverStripe\\CMS\\Controllers\\SilverStripeNavigatorItem']);
// Sort menu items according to priority
foreach($classes as $class) {
/** @var SilverStripeNavigatorItem $item */
$item = new $class($this->record);
if(!$item->canView()) {
continue;
}
// This funny litle formula ensures that the first item added with the same priority will be left-most.
$priority = $item->getPriority() * 100 - 1;
// Ensure that we can have duplicates with the same (default) priority
while(isset($items[$priority])) {
$priority++;
}
$items[$priority] = $item;
}
ksort($items);
// Drop the keys and let the ArrayList handle the numbering, so $First, $Last and others work properly.
return new ArrayList(array_values($items));
}
/**
* @return DataObject|CMSPreviewable
*/
public function getRecord() {
return $this->record;
}
/**
* @param DataObject|CMSPreviewable $record
* @return array template data
*/
static public function get_for_record($record) {
$html = '';
$message = '';
$navigator = new SilverStripeNavigator($record);
$items = $navigator->getItems();
foreach($items as $item) {
$text = $item->getHTML();
if($text) $html .= $text;
$newMessage = $item->getMessage();
if($newMessage && $item->isActive()) $message = $newMessage;
}
return array(
'items' => $html,
'message' => $message
);
}
}
/**
* Navigator items are links that appear in the $SilverStripeNavigator bar.
* To add an item, extend this class - it will be automatically picked up.
* When instanciating items manually, please ensure to call {@link canView()}.
*
* @package cms
* @subpackage content
*/
abstract class SilverStripeNavigatorItem extends ViewableData {
/**
* @param DataObject|CMSPreviewable
*/
protected $record;
/** @var string */
protected $recordLink;
/**
* @param DataObject|CMSPreviewable $record
*/
public function __construct(CMSPreviewable $record) {
parent::__construct();
$this->record = $record;
}
/**
* @return string HTML, mostly a link - but can be more complex as well.
* For example, a "future state" item might show a date selector.
*/
abstract public function getHTML();
/**
* @return string
* Get the Title of an item
*/
abstract public function getTitle();
/**
* Machine-friendly name.
*
* @return string
*/
public function getName() {
return substr(get_class($this), strpos(get_class($this), '_')+1);
}
/**
* Optional link to a specific view of this record.
* Not all items are simple links, please use {@link getHTML()}
* to represent an item in markup unless you know what you're doing.
*
* @return string
*/
public function getLink() {}
/**
* @return string
*/
public function getMessage() {}
/**
* @return DataObject
*/
public function getRecord() {
return $this->record;
}
/**
* @return int
*/
public function getPriority() {
return $this->stat('priority');
}
/**
* As items might convey different record states like a "stage" or "live" table,
* an item can be active (showing the record in this state).
*
* @return boolean
*/
public function isActive() {
return false;
}
/**
* Filters items based on member permissions or other criteria,
* such as if a state is generally available for the current record.
*
* @param Member $member
* @return Boolean
*/
public function canView($member = null) {
return true;
}
/**
* Counts as "archived" if the current record is a different version from both live and draft.
*
* @return boolean
*/
public function isArchived() {
if(!$this->record->hasExtension('SilverStripe\ORM\Versioning\Versioned')) return false;
if(!isset($this->record->_cached_isArchived)) {
$baseClass = $this->record->baseClass();
$currentDraft = Versioned::get_by_stage($baseClass, Versioned::DRAFT)->byID($this->record->ID);
$currentLive = Versioned::get_by_stage($baseClass, Versioned::LIVE)->byID($this->record->ID);
$this->record->_cached_isArchived = (
(!$currentDraft || ($currentDraft && $this->record->Version != $currentDraft->Version))
&& (!$currentLive || ($currentLive && $this->record->Version != $currentLive->Version))
);
}
return $this->record->_cached_isArchived;
}
}
/**
* @package cms
* @subpackage content
*/
class SilverStripeNavigatorItem_CMSLink extends SilverStripeNavigatorItem {
/** @config */
private static $priority = 10;
public function getHTML() {
return sprintf(
'<a href="%s">%s</a>',
$this->record->CMSEditLink(),
_t('ContentController.CMS', 'CMS')
);
}
public function getTitle() {
return _t('ContentController.CMS', 'CMS', 'Used in navigation. Should be a short label');
}
public function getLink() {
return $this->record->CMSEditLink();
}
public function isActive() {
return (Controller::curr() instanceof LeftAndMain);
}
public function canView($member = null) {
return (
// Don't show in CMS
!(Controller::curr() instanceof LeftAndMain)
// Don't follow redirects in preview, they break the CMS editing form
&& !($this->record instanceof RedirectorPage)
);
}
}
/**
* @package cms
* @subpackage content
*/
class SilverStripeNavigatorItem_StageLink extends SilverStripeNavigatorItem {
/** @config */
private static $priority = 20;
public function getHTML() {
$draftPage = $this->getDraftPage();
if($draftPage) {
$this->recordLink = Controller::join_links($draftPage->AbsoluteLink(), "?stage=Stage");
return "<a ". ($this->isActive() ? 'class="current" ' : '') ."href=\"$this->recordLink\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
}
}
public function getTitle() {
return _t('ContentController.DRAFT', 'Draft', 'Used for the Switch between draft and published view mode. Needs to be a short label');
}
public function getMessage() {
return "<div id=\"SilverStripeNavigatorMessage\" title=\"". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message will not be shown to your visitors') ."\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</div>";
}
public function getLink() {
$date = Versioned::current_archived_date();
return Controller::join_links(
$this->record->PreviewLink(),
'?stage=Stage',
$date ? '?archiveDate=' . $date : null
);
}
public function canView($member = null) {
return (
$this->record->hasExtension('SilverStripe\ORM\Versioning\Versioned')
&& $this->getDraftPage()
// Don't follow redirects in preview, they break the CMS editing form
&& !($this->record instanceof RedirectorPage)
);
}
public function isActive() {
return (
Versioned::get_stage() == 'Stage'
&& !(ClassInfo::exists('SiteTreeFutureState') && SiteTreeFutureState::get_future_datetime())
&& !$this->isArchived()
);
}
protected function getDraftPage() {
$baseClass = $this->record->baseClass();
return Versioned::get_by_stage($baseClass, Versioned::DRAFT)->byID($this->record->ID);
}
}
/**
* @package cms
* @subpackage content
*/
class SilverStripeNavigatorItem_LiveLink extends SilverStripeNavigatorItem {
/** @config */
private static $priority = 30;
public function getHTML() {
$livePage = $this->getLivePage();
if($livePage) {
$this->recordLink = Controller::join_links($livePage->AbsoluteLink(), "?stage=Live");
return "<a ". ($this->isActive() ? 'class="current" ' : '') ."href=\"$this->recordLink\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
}
}
public function getTitle() {
return _t('ContentController.PUBLISHED', 'Published', 'Used for the Switch between draft and published view mode. Needs to be a short label');
}
public function getMessage() {
return "<div id=\"SilverStripeNavigatorMessage\" title=\"". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message will not be shown to your visitors') ."\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</div>";
}
public function getLink() {
return Controller::join_links($this->record->PreviewLink(), '?stage=Live');
}
public function canView($member = null) {
return (
$this->record->hasExtension('SilverStripe\ORM\Versioning\Versioned')
&& $this->getLivePage()
// Don't follow redirects in preview, they break the CMS editing form
&& !($this->record instanceof RedirectorPage)
);
}
public function isActive() {
return (
(!Versioned::get_stage() || Versioned::get_stage() == 'Live')
&& !$this->isArchived()
);
}
protected function getLivePage() {
$baseClass = $this->record->baseClass();
return Versioned::get_by_stage($baseClass, Versioned::LIVE)->byID($this->record->ID);
}
}
/**
* @package cms
* @subpackage content
*/
class SilverStripeNavigatorItem_ArchiveLink extends SilverStripeNavigatorItem {
/** @config */
private static $priority = 40;
public function getHTML() {
$this->recordLink = $this->record->AbsoluteLink();
return "<a class=\"ss-ui-button". ($this->isActive() ? ' current' : '') ."\" href=\"$this->recordLink?archiveDate={$this->record->LastEdited}\" target=\"_blank\">". _t('ContentController.ARCHIVEDSITE', 'Preview version') ."</a>";
}
public function getTitle() {
return _t('SilverStripeNavigator.ARCHIVED', 'Archived');
}
public function getMessage() {
if($date = Versioned::current_archived_date()) {
$dateObj = DBField::create_field('Datetime', $date);
return "<div id=\"SilverStripeNavigatorMessage\" title=\"". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message will not be shown to your visitors') ."\">". _t('ContentController.ARCHIVEDSITEFROM', 'Archived site from') ."<br>" . $dateObj->Nice() . "</div>";
}
}
public function getLink() {
return $this->record->PreviewLink() . '?archiveDate=' . urlencode($this->record->LastEdited);
}
public function canView($member = null) {
return (
$this->record->hasExtension('SilverStripe\ORM\Versioning\Versioned')
&& $this->isArchived()
// Don't follow redirects in preview, they break the CMS editing form
&& !($this->record instanceof RedirectorPage)
);
}
public function isActive() {
return $this->isArchived();
}
}