PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/code/controllers/SilverStripeNavigator.php

https://gitlab.com/brendan2/silverstripe-cms-3.3.2
PHP | 407 lines | 227 code | 61 blank | 119 comment | 33 complexity | 3bb5f7f0512396180b4582700071ee48 MD5 | raw file
  1. <?php
  2. /**
  3. * Utility class representing links to different views of a record
  4. * for CMS authors, usually for {@link SiteTree} objects with "stage" and "live" links.
  5. * Useful both in the CMS and alongside the page template (for logged in authors).
  6. * The class can be used for any {@link DataObject} subclass implementing the {@link CMSPreviewable} interface.
  7. *
  8. * New item types can be defined by extending the {@link SilverStripeNavigatorItem} class,
  9. * for example the "cmsworkflow" module defines a new "future state" item with a date selector
  10. * to view embargoed data at a future point in time. So the item doesn't always have to be a simple link.
  11. *
  12. * @package cms
  13. * @subpackage content
  14. */
  15. class SilverStripeNavigator extends ViewableData {
  16. /**
  17. * @var DataObject
  18. */
  19. protected $record;
  20. /**
  21. * @param DataObject $record
  22. * @throws InvalidArgumentException if record doesn't implement CMSPreviewable
  23. */
  24. public function __construct($record) {
  25. if(!in_array('CMSPreviewable', class_implements($record))) {
  26. throw new InvalidArgumentException(sprintf(
  27. 'SilverStripeNavigator: Record of type %s doesn\'t implement CMSPreviewable',
  28. get_class($record)
  29. ));
  30. }
  31. $this->record = $record;
  32. }
  33. /**
  34. * @return SS_List of SilverStripeNavigatorItem
  35. */
  36. public function getItems() {
  37. $items = array();
  38. $classes = ClassInfo::subclassesFor('SilverStripeNavigatorItem');
  39. array_shift($classes);
  40. // Sort menu items according to priority
  41. $i = 0;
  42. foreach($classes as $class) {
  43. // Skip base class
  44. if($class == 'SilverStripeNavigatorItem') continue;
  45. $i++;
  46. $item = new $class($this->record);
  47. if(!$item->canView()) continue;
  48. // This funny litle formula ensures that the first item added with the same priority will be left-most.
  49. $priority = $item->getPriority() * 100 - 1;
  50. // Ensure that we can have duplicates with the same (default) priority
  51. while(isset($items[$priority])) $priority++;
  52. $items[$priority] = $item;
  53. }
  54. ksort($items);
  55. // Drop the keys and let the ArrayList handle the numbering, so $First, $Last and others work properly.
  56. return new ArrayList(array_values($items));
  57. }
  58. /**
  59. * @return DataObject
  60. */
  61. public function getRecord() {
  62. return $this->record;
  63. }
  64. /**
  65. * @param DataObject $record
  66. * @return Array template data
  67. */
  68. static public function get_for_record($record) {
  69. $html = '';
  70. $message = '';
  71. $navigator = new SilverStripeNavigator($record);
  72. $items = $navigator->getItems();
  73. foreach($items as $item) {
  74. $text = $item->getHTML();
  75. if($text) $html .= $text;
  76. $newMessage = $item->getMessage();
  77. if($newMessage && $item->isActive()) $message = $newMessage;
  78. }
  79. return array(
  80. 'items' => $html,
  81. 'message' => $message
  82. );
  83. }
  84. }
  85. /**
  86. * Navigator items are links that appear in the $SilverStripeNavigator bar.
  87. * To add an item, extend this class - it will be automatically picked up.
  88. * When instanciating items manually, please ensure to call {@link canView()}.
  89. *
  90. * @package cms
  91. * @subpackage content
  92. */
  93. class SilverStripeNavigatorItem extends ViewableData {
  94. /**
  95. * @param DataObject
  96. */
  97. protected $record;
  98. /**
  99. * @param DataObject
  100. */
  101. public function __construct($record) {
  102. $this->record = $record;
  103. }
  104. /**
  105. * @return string HTML, mostly a link - but can be more complex as well.
  106. * For example, a "future state" item might show a date selector.
  107. */
  108. public function getHTML() {}
  109. /**
  110. * @return string
  111. * Get the Title of an item
  112. */
  113. public function getTitle() {}
  114. /**
  115. * Machine-friendly name.
  116. */
  117. public function getName() {
  118. return substr(get_class($this), strpos(get_class($this), '_')+1);
  119. }
  120. /**
  121. * Optional link to a specific view of this record.
  122. * Not all items are simple links, please use {@link getHTML()}
  123. * to represent an item in markup unless you know what you're doing.
  124. *
  125. * @return string
  126. */
  127. public function getLink() {}
  128. /**
  129. * @return string
  130. */
  131. public function getMessage() {}
  132. /**
  133. * @return DataObject
  134. */
  135. public function getRecord() {
  136. return $this->record;
  137. }
  138. /**
  139. * @return int
  140. */
  141. public function getPriority() {
  142. return $this->stat('priority');
  143. }
  144. /**
  145. * As items might convey different record states like a "stage" or "live" table,
  146. * an item can be active (showing the record in this state).
  147. *
  148. * @return boolean
  149. */
  150. public function isActive() {
  151. return false;
  152. }
  153. /**
  154. * Filters items based on member permissions or other criteria,
  155. * such as if a state is generally available for the current record.
  156. *
  157. * @param Member
  158. * @return Boolean
  159. */
  160. public function canView($member = null) {
  161. return true;
  162. }
  163. /**
  164. * Counts as "archived" if the current record is a different version from both live and draft.
  165. *
  166. * @return boolean
  167. */
  168. public function isArchived() {
  169. if(!$this->record->hasExtension('Versioned')) return false;
  170. if(!isset($this->record->_cached_isArchived)) {
  171. $baseTable = ClassInfo::baseDataClass($this->record->class);
  172. $currentDraft = Versioned::get_one_by_stage($baseTable, 'Stage', array(
  173. "\"$baseTable\".\"ID\"" => $this->record->ID
  174. ));
  175. $currentLive = Versioned::get_one_by_stage($baseTable, 'Live', array(
  176. "\"$baseTable\".\"ID\"" => $this->record->ID
  177. ));
  178. $this->record->_cached_isArchived = (
  179. (!$currentDraft || ($currentDraft && $this->record->Version != $currentDraft->Version))
  180. && (!$currentLive || ($currentLive && $this->record->Version != $currentLive->Version))
  181. );
  182. }
  183. return $this->record->_cached_isArchived;
  184. }
  185. }
  186. /**
  187. * @package cms
  188. * @subpackage content
  189. */
  190. class SilverStripeNavigatorItem_CMSLink extends SilverStripeNavigatorItem {
  191. /** @config */
  192. private static $priority = 10;
  193. public function getHTML() {
  194. return sprintf(
  195. '<a href="%s">%s</a>',
  196. $this->record->CMSEditLink(),
  197. _t('ContentController.CMS', 'CMS')
  198. );
  199. }
  200. public function getTitle() {
  201. return _t('ContentController.CMS', 'CMS', 'Used in navigation. Should be a short label');
  202. }
  203. public function getLink() {
  204. return $this->record->CMSEditLink();
  205. }
  206. public function isActive() {
  207. return (Controller::curr() instanceof LeftAndMain);
  208. }
  209. public function canView($member = null) {
  210. return (
  211. // Don't show in CMS
  212. !(Controller::curr() instanceof LeftAndMain)
  213. // Don't follow redirects in preview, they break the CMS editing form
  214. && !($this->record instanceof RedirectorPage)
  215. );
  216. }
  217. }
  218. /**
  219. * @package cms
  220. * @subpackage content
  221. */
  222. class SilverStripeNavigatorItem_StageLink extends SilverStripeNavigatorItem {
  223. /** @config */
  224. private static $priority = 20;
  225. public function getHTML() {
  226. $draftPage = $this->getDraftPage();
  227. if($draftPage) {
  228. $this->recordLink = Controller::join_links($draftPage->AbsoluteLink(), "?stage=Stage");
  229. return "<a ". ($this->isActive() ? 'class="current" ' : '') ."href=\"$this->recordLink\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
  230. }
  231. }
  232. public function getTitle() {
  233. return _t('ContentController.DRAFT', 'Draft', 'Used for the Switch between draft and published view mode. Needs to be a short label');
  234. }
  235. public function getMessage() {
  236. return "<div id=\"SilverStripeNavigatorMessage\" title=\"". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message will not be shown to your visitors') ."\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</div>";
  237. }
  238. public function getLink() {
  239. $date = Versioned::current_archived_date();
  240. return Controller::join_links(
  241. $this->record->PreviewLink(),
  242. '?stage=Stage',
  243. $date ? '?archiveDate=' . $date : null
  244. );
  245. }
  246. public function canView($member = null) {
  247. return (
  248. $this->record->hasExtension('Versioned')
  249. && $this->getDraftPage()
  250. // Don't follow redirects in preview, they break the CMS editing form
  251. && !($this->record instanceof RedirectorPage)
  252. );
  253. }
  254. public function isActive() {
  255. return (
  256. Versioned::current_stage() == 'Stage'
  257. && !(ClassInfo::exists('SiteTreeFutureState') && SiteTreeFutureState::get_future_datetime())
  258. && !$this->isArchived()
  259. );
  260. }
  261. protected function getDraftPage() {
  262. $baseTable = ClassInfo::baseDataClass($this->record->class);
  263. return Versioned::get_one_by_stage($baseTable, 'Stage', array(
  264. "\"$baseTable\".\"ID\"" => $this->record->ID
  265. ));
  266. }
  267. }
  268. /**
  269. * @package cms
  270. * @subpackage content
  271. */
  272. class SilverStripeNavigatorItem_LiveLink extends SilverStripeNavigatorItem {
  273. /** @config */
  274. private static $priority = 30;
  275. public function getHTML() {
  276. $livePage = $this->getLivePage();
  277. if($livePage) {
  278. $this->recordLink = Controller::join_links($livePage->AbsoluteLink(), "?stage=Live");
  279. return "<a ". ($this->isActive() ? 'class="current" ' : '') ."href=\"$this->recordLink\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
  280. }
  281. }
  282. public function getTitle() {
  283. return _t('ContentController.PUBLISHED', 'Published', 'Used for the Switch between draft and published view mode. Needs to be a short label');
  284. }
  285. public function getMessage() {
  286. return "<div id=\"SilverStripeNavigatorMessage\" title=\"". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message will not be shown to your visitors') ."\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</div>";
  287. }
  288. public function getLink() {
  289. return Controller::join_links($this->record->PreviewLink(), '?stage=Live');
  290. }
  291. public function canView($member = null) {
  292. return (
  293. $this->record->hasExtension('Versioned')
  294. && $this->getLivePage()
  295. // Don't follow redirects in preview, they break the CMS editing form
  296. && !($this->record instanceof RedirectorPage)
  297. );
  298. }
  299. public function isActive() {
  300. return (
  301. (!Versioned::current_stage() || Versioned::current_stage() == 'Live')
  302. && !$this->isArchived()
  303. );
  304. }
  305. protected function getLivePage() {
  306. $baseTable = ClassInfo::baseDataClass($this->record->class);
  307. return Versioned::get_one_by_stage($baseTable, 'Live', array(
  308. "\"$baseTable\".\"ID\"" => $this->record->ID
  309. ));
  310. }
  311. }
  312. /**
  313. * @package cms
  314. * @subpackage content
  315. */
  316. class SilverStripeNavigatorItem_ArchiveLink extends SilverStripeNavigatorItem {
  317. /** @config */
  318. private static $priority = 40;
  319. public function getHTML() {
  320. $this->recordLink = $this->record->AbsoluteLink();
  321. return "<a class=\"ss-ui-button". ($this->isActive() ? ' current' : '') ."\" href=\"$this->recordLink?archiveDate={$this->record->LastEdited}\" target=\"_blank\">". _t('ContentController.ARCHIVEDSITE', 'Preview version') ."</a>";
  322. }
  323. public function getTitle() {
  324. return _t('SilverStripeNavigator.ARCHIVED', 'Archived');
  325. }
  326. public function getMessage() {
  327. if($date = Versioned::current_archived_date()) {
  328. $dateObj = DBField::create_field('Datetime', $date);
  329. 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>";
  330. }
  331. }
  332. public function getLink() {
  333. return $this->record->PreviewLink() . '?archiveDate=' . urlencode($this->record->LastEdited);
  334. }
  335. public function canView($member = null) {
  336. return (
  337. $this->record->hasExtension('Versioned')
  338. && $this->isArchived()
  339. // Don't follow redirects in preview, they break the CMS editing form
  340. && !($this->record instanceof RedirectorPage)
  341. );
  342. }
  343. public function isActive() {
  344. return $this->isArchived();
  345. }
  346. }