BUG Fix link concatenation in SilverStripeNavigator (#1560)

Fixes #1557
Fixes https://github.com/tractorcow/silverstripe-fluent/issues/219
This commit is contained in:
Damian Mooyman 2016-07-26 01:34:45 +12:00 committed by Daniel Hensby
parent a8cf17b008
commit 3306deb69b

View File

@ -4,33 +4,34 @@
* for CMS authors, usually for {@link SiteTree} objects with "stage" and "live" links. * 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). * 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. * 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, * 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 * 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. * to view embargoed data at a future point in time. So the item doesn't always have to be a simple link.
* *
* @package cms * @package cms
* @subpackage content * @subpackage content
*/ */
class SilverStripeNavigator extends ViewableData { class SilverStripeNavigator extends ViewableData {
/** /**
* @var DataObject * @var DataObject|CMSPreviewable
*/ */
protected $record; protected $record;
/** /**
* @param DataObject $record * @param DataObject $record
* @throws InvalidArgumentException if record doesn't implement CMSPreviewable * @throws InvalidArgumentException if record doesn't implement CMSPreviewable
*/ */
public function __construct($record) { public function __construct($record) {
if(!in_array('CMSPreviewable', class_implements($record))) { parent::__construct();
if (!($record instanceof CMSPreviewable)) {
throw new InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
'SilverStripeNavigator: Record of type %s doesn\'t implement CMSPreviewable', 'SilverStripeNavigator: Record of type %s doesn\'t implement CMSPreviewable',
get_class($record) get_class($record)
)); ));
} }
$this->record = $record; $this->record = $record;
} }
@ -39,26 +40,27 @@ class SilverStripeNavigator extends ViewableData {
*/ */
public function getItems() { public function getItems() {
$items = array(); $items = array();
$classes = ClassInfo::subclassesFor('SilverStripeNavigatorItem'); $classes = ClassInfo::subclassesFor('SilverStripeNavigatorItem');
array_shift($classes); array_shift($classes);
// Sort menu items according to priority // Sort menu items according to priority
$i = 0; $i = 0;
foreach($classes as $class) { foreach($classes as $class) {
// Skip base class // Skip base class
if($class == 'SilverStripeNavigatorItem') continue; if($class == 'SilverStripeNavigatorItem') continue;
$i++; $i++;
/** @var SilverStripeNavigatorItem $item */
$item = new $class($this->record); $item = new $class($this->record);
if(!$item->canView()) continue; if(!$item->canView()) continue;
// This funny litle formula ensures that the first item added with the same priority will be left-most. // This funny litle formula ensures that the first item added with the same priority will be left-most.
$priority = $item->getPriority() * 100 - 1; $priority = $item->getPriority() * 100 - 1;
// Ensure that we can have duplicates with the same (default) priority // Ensure that we can have duplicates with the same (default) priority
while(isset($items[$priority])) $priority++; while(isset($items[$priority])) $priority++;
$items[$priority] = $item; $items[$priority] = $item;
} }
ksort($items); ksort($items);
@ -66,7 +68,7 @@ class SilverStripeNavigator extends ViewableData {
// Drop the keys and let the ArrayList handle the numbering, so $First, $Last and others work properly. // Drop the keys and let the ArrayList handle the numbering, so $First, $Last and others work properly.
return new ArrayList(array_values($items)); return new ArrayList(array_values($items));
} }
/** /**
* @return DataObject * @return DataObject
*/ */
@ -76,20 +78,20 @@ class SilverStripeNavigator extends ViewableData {
/** /**
* @param DataObject $record * @param DataObject $record
* @return Array template data * @return array template data
*/ */
static public function get_for_record($record) { static public function get_for_record($record) {
$html = ''; $html = '';
$message = ''; $message = '';
$navigator = new SilverStripeNavigator($record); $navigator = new SilverStripeNavigator($record);
$items = $navigator->getItems(); $items = $navigator->getItems();
foreach($items as $item) { foreach($items as $item) {
$text = $item->getHTML(); $text = $item->getHTML();
if($text) $html .= $text; if($text) $html .= $text;
$newMessage = $item->getMessage(); $newMessage = $item->getMessage();
if($newMessage && $item->isActive()) $message = $newMessage; if($newMessage && $item->isActive()) $message = $newMessage;
} }
return array( return array(
'items' => $html, 'items' => $html,
'message' => $message 'message' => $message
@ -101,24 +103,25 @@ class SilverStripeNavigator extends ViewableData {
* Navigator items are links that appear in the $SilverStripeNavigator bar. * Navigator items are links that appear in the $SilverStripeNavigator bar.
* To add an item, extend this class - it will be automatically picked up. * To add an item, extend this class - it will be automatically picked up.
* When instanciating items manually, please ensure to call {@link canView()}. * When instanciating items manually, please ensure to call {@link canView()}.
* *
* @package cms * @package cms
* @subpackage content * @subpackage content
*/ */
class SilverStripeNavigatorItem extends ViewableData { class SilverStripeNavigatorItem extends ViewableData {
/** /**
* @param DataObject * @var DataObject|CMSPreviewable
*/ */
protected $record; protected $record;
/** /**
* @param DataObject * @param DataObject $record
*/ */
public function __construct($record) { public function __construct($record) {
parent::__construct();
$this->record = $record; $this->record = $record;
} }
/** /**
* @return string HTML, mostly a link - but can be more complex as well. * @return string HTML, mostly a link - but can be more complex as well.
* For example, a "future state" item might show a date selector. * For example, a "future state" item might show a date selector.
@ -130,7 +133,7 @@ class SilverStripeNavigatorItem extends ViewableData {
* Get the Title of an item * Get the Title of an item
*/ */
public function getTitle() {} public function getTitle() {}
/** /**
* Machine-friendly name. * Machine-friendly name.
*/ */
@ -142,44 +145,44 @@ class SilverStripeNavigatorItem extends ViewableData {
* Optional link to a specific view of this record. * Optional link to a specific view of this record.
* Not all items are simple links, please use {@link getHTML()} * Not all items are simple links, please use {@link getHTML()}
* to represent an item in markup unless you know what you're doing. * to represent an item in markup unless you know what you're doing.
* *
* @return string * @return string
*/ */
public function getLink() {} public function getLink() {}
/** /**
* @return string * @return string
*/ */
public function getMessage() {} public function getMessage() {}
/** /**
* @return DataObject * @return DataObject
*/ */
public function getRecord() { public function getRecord() {
return $this->record; return $this->record;
} }
/** /**
* @return int * @return int
*/ */
public function getPriority() { public function getPriority() {
return $this->stat('priority'); return $this->stat('priority');
} }
/** /**
* As items might convey different record states like a "stage" or "live" table, * As items might convey different record states like a "stage" or "live" table,
* an item can be active (showing the record in this state). * an item can be active (showing the record in this state).
* *
* @return boolean * @return boolean
*/ */
public function isActive() { public function isActive() {
return false; return false;
} }
/** /**
* Filters items based on member permissions or other criteria, * Filters items based on member permissions or other criteria,
* such as if a state is generally available for the current record. * such as if a state is generally available for the current record.
* *
* @param Member * @param Member
* @return Boolean * @return Boolean
*/ */
@ -189,12 +192,12 @@ class SilverStripeNavigatorItem extends ViewableData {
/** /**
* Counts as "archived" if the current record is a different version from both live and draft. * Counts as "archived" if the current record is a different version from both live and draft.
* *
* @return boolean * @return boolean
*/ */
public function isArchived() { public function isArchived() {
if(!$this->record->hasExtension('Versioned')) return false; if(!$this->record->hasExtension('Versioned')) return false;
if(!isset($this->record->_cached_isArchived)) { if(!isset($this->record->_cached_isArchived)) {
$baseTable = ClassInfo::baseDataClass($this->record->class); $baseTable = ClassInfo::baseDataClass($this->record->class);
$currentDraft = Versioned::get_one_by_stage($baseTable, 'Stage', array( $currentDraft = Versioned::get_one_by_stage($baseTable, 'Stage', array(
@ -203,12 +206,12 @@ class SilverStripeNavigatorItem extends ViewableData {
$currentLive = Versioned::get_one_by_stage($baseTable, 'Live', array( $currentLive = Versioned::get_one_by_stage($baseTable, 'Live', array(
"\"$baseTable\".\"ID\"" => $this->record->ID "\"$baseTable\".\"ID\"" => $this->record->ID
)); ));
$this->record->_cached_isArchived = ( $this->record->_cached_isArchived = (
(!$currentDraft || ($currentDraft && $this->record->Version != $currentDraft->Version)) (!$currentDraft || ($currentDraft && $this->record->Version != $currentDraft->Version))
&& (!$currentLive || ($currentLive && $this->record->Version != $currentLive->Version)) && (!$currentLive || ($currentLive && $this->record->Version != $currentLive->Version))
); );
} }
return $this->record->_cached_isArchived; return $this->record->_cached_isArchived;
} }
@ -220,8 +223,8 @@ class SilverStripeNavigatorItem extends ViewableData {
*/ */
class SilverStripeNavigatorItem_CMSLink extends SilverStripeNavigatorItem { class SilverStripeNavigatorItem_CMSLink extends SilverStripeNavigatorItem {
/** @config */ /** @config */
private static $priority = 10; private static $priority = 10;
public function getHTML() { public function getHTML() {
return sprintf( return sprintf(
'<a href="%s">%s</a>', '<a href="%s">%s</a>',
@ -229,19 +232,19 @@ class SilverStripeNavigatorItem_CMSLink extends SilverStripeNavigatorItem {
_t('ContentController.CMS', 'CMS') _t('ContentController.CMS', 'CMS')
); );
} }
public function getTitle() { public function getTitle() {
return _t('ContentController.CMS', 'CMS', 'Used in navigation. Should be a short label'); return _t('ContentController.CMS', 'CMS', 'Used in navigation. Should be a short label');
} }
public function getLink() { public function getLink() {
return $this->record->CMSEditLink(); return $this->record->CMSEditLink();
} }
public function isActive() { public function isActive() {
return (Controller::curr() instanceof LeftAndMain); return (Controller::curr() instanceof LeftAndMain);
} }
public function canView($member = null) { public function canView($member = null) {
return ( return (
// Don't show in CMS // Don't show in CMS
@ -250,7 +253,6 @@ class SilverStripeNavigatorItem_CMSLink extends SilverStripeNavigatorItem {
&& !($this->record instanceof RedirectorPage) && !($this->record instanceof RedirectorPage)
); );
} }
} }
/** /**
@ -263,46 +265,50 @@ class SilverStripeNavigatorItem_StageLink extends SilverStripeNavigatorItem {
public function getHTML() { public function getHTML() {
$draftPage = $this->getDraftPage(); $draftPage = $this->getDraftPage();
if($draftPage) { if (!$draftPage) {
$this->recordLink = Controller::join_links($draftPage->AbsoluteLink(), "?stage=Stage"); return null;
return "<a ". ($this->isActive() ? 'class="current" ' : '') ."href=\"$this->recordLink\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
} }
$linkClass = $this->isActive() ? 'class="current" ' : '';
$linkTitle = _t('ContentController.DRAFTSITE', 'Draft Site');
$recordLink = Convert::raw2att(Controller::join_links($draftPage->AbsoluteLink(), "?stage=Stage"));
return "<a {$linkClass} href=\"$recordLink\">$linkTitle</a>";
} }
public function getTitle() { public function getTitle() {
return _t('ContentController.DRAFT', 'Draft', 'Used for the Switch between draft and published view mode. Needs to be a short label'); return _t('ContentController.DRAFT', 'Draft', 'Used for the Switch between draft and published view mode. Needs to be a short label');
} }
public function getMessage() { 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>"; 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() { public function getLink() {
$date = Versioned::current_archived_date(); $date = Versioned::current_archived_date();
return Controller::join_links( return Controller::join_links(
$this->record->PreviewLink(), $this->record->PreviewLink(),
'?stage=Stage', '?stage=Stage',
$date ? '?archiveDate=' . $date : null $date ? '?archiveDate=' . $date : null
); );
} }
public function canView($member = null) { public function canView($member = null) {
return ( return (
$this->record->hasExtension('Versioned') $this->record->hasExtension('Versioned')
&& $this->getDraftPage() && $this->getDraftPage()
// Don't follow redirects in preview, they break the CMS editing form // Don't follow redirects in preview, they break the CMS editing form
&& !($this->record instanceof RedirectorPage) && !($this->record instanceof RedirectorPage)
); );
} }
public function isActive() { public function isActive() {
return ( return (
Versioned::current_stage() == 'Stage' Versioned::current_stage() == 'Stage'
&& !(ClassInfo::exists('SiteTreeFutureState') && SiteTreeFutureState::get_future_datetime()) && !(ClassInfo::exists('SiteTreeFutureState') && SiteTreeFutureState::get_future_datetime())
&& !$this->isArchived() && !$this->isArchived()
); );
} }
protected function getDraftPage() { protected function getDraftPage() {
$baseTable = ClassInfo::baseDataClass($this->record->class); $baseTable = ClassInfo::baseDataClass($this->record->class);
return Versioned::get_one_by_stage($baseTable, 'Stage', array( return Versioned::get_one_by_stage($baseTable, 'Stage', array(
@ -321,40 +327,44 @@ class SilverStripeNavigatorItem_LiveLink extends SilverStripeNavigatorItem {
public function getHTML() { public function getHTML() {
$livePage = $this->getLivePage(); $livePage = $this->getLivePage();
if($livePage) { if (!$livePage) {
$this->recordLink = Controller::join_links($livePage->AbsoluteLink(), "?stage=Live"); return null;
return "<a ". ($this->isActive() ? 'class="current" ' : '') ."href=\"$this->recordLink\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
} }
$linkClass = $this->isActive() ? 'class="current" ' : '';
$linkTitle = _t('ContentController.PUBLISHEDSITE', 'Published Site');
$recordLink = Convert::raw2att(Controller::join_links($livePage->AbsoluteLink(), "?stage=Live"));
return "<a {$linkClass} href=\"$recordLink\">$linkTitle</a>";
} }
public function getTitle() { public function getTitle() {
return _t('ContentController.PUBLISHED', 'Published', 'Used for the Switch between draft and published view mode. Needs to be a short label'); return _t('ContentController.PUBLISHED', 'Published', 'Used for the Switch between draft and published view mode. Needs to be a short label');
} }
public function getMessage() { 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>"; 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() { public function getLink() {
return Controller::join_links($this->record->PreviewLink(), '?stage=Live'); return Controller::join_links($this->record->PreviewLink(), '?stage=Live');
} }
public function canView($member = null) { public function canView($member = null) {
return ( return (
$this->record->hasExtension('Versioned') $this->record->hasExtension('Versioned')
&& $this->getLivePage() && $this->getLivePage()
// Don't follow redirects in preview, they break the CMS editing form // Don't follow redirects in preview, they break the CMS editing form
&& !($this->record instanceof RedirectorPage) && !($this->record instanceof RedirectorPage)
); );
} }
public function isActive() { public function isActive() {
return ( return (
(!Versioned::current_stage() || Versioned::current_stage() == 'Live') (!Versioned::current_stage() || Versioned::current_stage() == 'Live')
&& !$this->isArchived() && !$this->isArchived()
); );
} }
protected function getLivePage() { protected function getLivePage() {
$baseTable = ClassInfo::baseDataClass($this->record->class); $baseTable = ClassInfo::baseDataClass($this->record->class);
return Versioned::get_one_by_stage($baseTable, 'Live', array( return Versioned::get_one_by_stage($baseTable, 'Live', array(
@ -372,36 +382,50 @@ class SilverStripeNavigatorItem_ArchiveLink extends SilverStripeNavigatorItem {
private static $priority = 40; private static $priority = 40;
public function getHTML() { public function getHTML() {
$this->recordLink = $this->record->AbsoluteLink(); $linkClass = $this->isActive() ? 'ss-ui-button current' : 'ss-ui-button';
return "<a class=\"ss-ui-button". ($this->isActive() ? ' current' : '') ."\" href=\"$this->recordLink?archiveDate={$this->record->LastEdited}\" target=\"_blank\">". _t('ContentController.ARCHIVEDSITE', 'Preview version') ."</a>"; $linkTitle = _t('ContentController.ARCHIVEDSITE', 'Preview version');
$recordLink = Convert::raw2att(Controller::join_links(
$this->record->AbsoluteLink(),
'?archiveDate=' . urlencode($this->record->LastEdited)
));
return "<a class=\"{$linkClass}\" href=\"$recordLink\" target=\"_blank\">$linkTitle</a>";
} }
public function getTitle() { public function getTitle() {
return _t('SilverStripeNavigator.ARCHIVED', 'Archived'); return _t('SilverStripeNavigator.ARCHIVED', 'Archived');
} }
public function getMessage() { public function getMessage() {
if($date = Versioned::current_archived_date()) { $date = Versioned::current_archived_date();
$dateObj = DBField::create_field('Datetime', $date); if (empty($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>"; return null;
} }
/** @var SS_Datetime $dateObj */
$dateObj = DBField::create_field('Datetime', $date);
$title = _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message will not be shown to your visitors');
return "<div id=\"SilverStripeNavigatorMessage\" title=\"{$title}\">"
. _t('ContentController.ARCHIVEDSITEFROM', 'Archived site from')
. "<br />" . $dateObj->Nice() . "</div>";
} }
public function getLink() { public function getLink() {
return $this->record->PreviewLink() . '?archiveDate=' . urlencode($this->record->LastEdited); return Controller::join_links(
$this->record->PreviewLink(),
'?archiveDate=' . urlencode($this->record->LastEdited)
);
} }
public function canView($member = null) { public function canView($member = null) {
return ( return (
$this->record->hasExtension('Versioned') $this->record->hasExtension('Versioned')
&& $this->isArchived() && $this->isArchived()
// Don't follow redirects in preview, they break the CMS editing form // Don't follow redirects in preview, they break the CMS editing form
&& !($this->record instanceof RedirectorPage) && !($this->record instanceof RedirectorPage)
); );
} }
public function isActive() { public function isActive() {
return $this->isArchived(); return $this->isArchived();
} }
} }