1 <?php
2 3 4 5 6 7 8 9
10 class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvider {
11
12 13 14 15 16 17 18 19 20 21
22 static $allowed_children = array("SiteTree");
23
24 25 26 27 28
29 static $default_child = "Page";
30
31 32 33 34 35
36 static $default_parent = null;
37
38 39 40 41 42
43 static $can_be_root = true;
44
45 46 47 48 49 50
51 static $need_permission = null;
52
53 54 55 56 57 58 59
60 static $hide_ancestor = null;
61
62 static $db = array(
63 "URLSegment" => "Varchar(255)",
64 "Title" => "Varchar(255)",
65 "MenuTitle" => "Varchar(100)",
66 "Content" => "HTMLText",
67 "MetaTitle" => "Varchar(255)",
68 "MetaDescription" => "Text",
69 "MetaKeywords" => "Varchar(255)",
70 "ExtraMeta" => "HTMLText",
71 "ShowInMenus" => "Boolean",
72 "ShowInSearch" => "Boolean",
73 "HomepageForDomain" => "Varchar(100)",
74 "ProvideComments" => "Boolean",
75 "Sort" => "Int",
76 "HasBrokenFile" => "Boolean",
77 "HasBrokenLink" => "Boolean",
78 "Status" => "Varchar",
79 "ReportClass" => "Varchar",
80 "CanViewType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers, Inherit', 'Inherit')",
81 "CanEditType" => "Enum('LoggedInUsers, OnlyTheseUsers, Inherit', 'Inherit')",
82
83
84 "ToDo" => "Text",
85 'NumberCMSChildren' => 'Int',
86 );
87
88 static $indexes = array(
89 "LastEdited" => true,
90 "URLSegment" => true,
91 );
92
93 static $has_many = array(
94 "Comments" => "PageComment"
95 );
96
97 static $many_many = array(
98 "LinkTracking" => "SiteTree",
99 "ViewerGroups" => "Group",
100 "EditorGroups" => "Group",
101 );
102
103 static $belongs_many_many = array(
104 "BackLinkTracking" => "SiteTree"
105 );
106
107 static = array(
108 "LinkTracking" => array("FieldName" => "Varchar"),
109 );
110
111 static $casting = array(
112 "Breadcrumbs" => "HTMLText",
113 "LastEdited" => "SS_Datetime",
114 "Created" => "SS_Datetime",
115 );
116
117 static $defaults = array(
118 "ShowInMenus" => 1,
119 "ShowInSearch" => 1,
120 "Status" => "New page",
121 "CanViewType" => "Inherit",
122 "CanEditType" => "Inherit"
123 );
124
125 static $has_one = array(
126 "Parent" => "SiteTree"
127 );
128
129 static $versioning = array(
130 "Stage", "Live"
131 );
132
133 static $default_sort = "\"Sort\"";
134
135 136 137 138
139 static $can_create = true;
140
141
142 143 144 145 146 147 148 149 150 151 152 153 154
155 static $icon = array("sapphire/javascript/tree/images/page", "file");
156
157
158 static $extensions = array(
159 "Hierarchy",
160 "Versioned('Stage', 'Live')",
161 );
162
163 164 165 166 167
168 public static $breadcrumbs_delimiter = " » ";
169
170 171 172
173 public static $write_homepage_map = true;
174
175 static $searchable_fields = array(
176 'Title',
177 'Content',
178 );
179
180 181 182
183 private static $nested_urls = false;
184
185 186 187
188 private static $runCMSFieldsExtensions = true;
189
190 191 192
193 public static $cache_permissions = array();
194
195 196 197
198 private static $enforce_strict_hierarchy = true;
199
200 public static function set_enforce_strict_hierarchy($to) {
201 self::$enforce_strict_hierarchy = $to;
202 }
203
204 public static function get_enforce_strict_hierarchy() {
205 return self::$enforce_strict_hierarchy;
206 }
207
208 209 210 211 212
213 public static function nested_urls() {
214 return self::$nested_urls;
215 }
216
217 public static function enable_nested_urls() {
218 self::$nested_urls = true;
219 }
220
221 public static function disable_nested_urls() {
222 self::$nested_urls = false;
223 }
224
225 226 227 228 229 230 231 232 233 234 235 236 237
238 public static function get_by_link($link, $cache = true) {
239 if(trim($link, '/')) {
240 $link = trim(Director::makeRelative($link), '/');
241 } else {
242 $link = RootURLController::get_homepage_link();
243 }
244
245 $parts = Convert::raw2sql(preg_split('|/+|', $link));
246
247
248 $URLSegment = array_shift($parts);
249 $sitetree = DataObject::get_one (
250 'SiteTree', "\"URLSegment\" = '$URLSegment'" . (self::nested_urls() ? ' AND "ParentID" = 0' : ''), $cache
251 );
252
253
254 if(!$sitetree && self::nested_urls() && $pages = DataObject::get('SiteTree', "\"URLSegment\" = '$URLSegment'")) {
255 return ($pages->Count() == 1) ? $pages->First() : null;
256 }
257
258
259 if(!$sitetree) {
260 $parentID = self::nested_urls() ? 0 : null;
261
262 if($alternatives = singleton('SiteTree')->extend('alternateGetByLink', $URLSegment, $parentID)) {
263 foreach($alternatives as $alternative) if($alternative) $sitetree = $alternative;
264 }
265
266 if(!$sitetree) return false;
267 }
268
269
270 if(!self::nested_urls() || !count($parts)) return $sitetree;
271
272
273 foreach($parts as $segment) {
274 $next = DataObject::get_one (
275 'SiteTree', "\"URLSegment\" = '$segment' AND \"ParentID\" = $sitetree->ID", $cache
276 );
277
278 if(!$next) {
279 $parentID = (int) $sitetree->ID;
280
281 if($alternatives = singleton('SiteTree')->extend('alternateGetByLink', $segment, $parentID)) {
282 foreach($alternatives as $alternative) if($alternative) $next = $alternative;
283 }
284
285 if(!$next) return false;
286 }
287
288 $sitetree->destroy();
289 $sitetree = $next;
290 }
291
292 return $sitetree;
293 }
294
295 296 297 298 299 300 301
302 public static function page_type_classes() {
303 $classes = ClassInfo::getValidSubClasses();
304
305 $baseClassIndex = array_search('SiteTree', $classes);
306 if($baseClassIndex !== FALSE) unset($classes[$baseClassIndex]);
307
308 $kill_ancestors = array();
309
310
311 foreach($classes as $class) {
312 $instance = singleton($class);
313
314
315 if($ancestor_to_hide = $instance->stat('hide_ancestor')) {
316
317 $kill_ancestors[] = $ancestor_to_hide;
318 }
319 }
320
321
322 if($kill_ancestors) {
323 $kill_ancestors = array_unique($kill_ancestors);
324 foreach($kill_ancestors as $mark) {
325
326 $idx = array_search($mark, $classes);
327 unset($classes[$idx]);
328 }
329 }
330
331 return $classes;
332 }
333
334 335 336 337 338
339 public static function link_shortcode_handler($arguments, $content = null, $parser = null) {
340 if(!isset($arguments['id']) || !is_numeric($arguments['id'])) return;
341
342 if (
343 !($page = DataObject::get_by_id('SiteTree', $arguments['id']))
344 && !($page = Versioned::get_latest_version('SiteTree', $arguments['id']))
345 && !($page = DataObject::get_one('ErrorPage', '"ErrorCode" = \'404\''))
346 ) {
347 return;
348 }
349
350 if($content) {
351 return sprintf('<a href="%s">%s</a>', $page->Link(), $parser->parse($content));
352 } else {
353 return $page->Link();
354 }
355 }
356
357 358 359 360 361 362
363 public function Link($action = null) {
364 return Controller::join_links(Director::baseURL(), $this->RelativeLink($action));
365 }
366
367 368 369 370 371 372
373 public function AbsoluteLink($action = null) {
374 if($this->hasMethod('alternateAbsoluteLink')) {
375 return $this->alternateAbsoluteLink($action);
376 } else {
377 return Director::absoluteURL($this->Link($action));
378 }
379 }
380
381 382 383 384 385 386 387 388 389 390 391
392 public function RelativeLink($action = null) {
393 if($this->ParentID && self::nested_urls()) {
394 $base = $this->Parent()->RelativeLink($this->URLSegment);
395 } else {
396 $base = $this->URLSegment;
397 }
398
399
400
401
402
403 if(!$action && $base == RootURLController::get_homepage_link() && !$this->ParentID) {
404 $base = null;
405 if($this->hasExtension('Translatable') && $this->Locale != Translatable::default_locale()){
406 $base = $this->URLSegment;
407 }
408 }
409
410 if(is_string($action)) {
411 $action = str_replace('&', '&', $action);
412 } elseif($action === true) {
413 $action = null;
414 }
415
416 return Controller::join_links($base, '/', $action);
417 }
418
419 420 421
422 public function getAbsoluteLiveLink($includeStageEqualsLive = true) {
423 $live = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $this->ID);
424
425 if($live) {
426 $link = $live->AbsoluteLink();
427
428 if($includeStageEqualsLive) {
429 $link .= '?stage=Live';
430 }
431
432 return $link;
433
434 }
435 }
436
437
438 439 440 441 442
443 public function ElementName() {
444 return str_replace('/', '-', trim($this->RelativeLink(true), '/'));
445 }
446
447 448 449 450 451
452 public function isCurrent() {
453 return $this->ID ? $this->ID == Director::get_current_page()->ID : $this === Director::get_current_page();
454 }
455
456 457 458 459 460 461
462 public function isSection() {
463 return $this->isCurrent() || (
464 Director::get_current_page() instanceof SiteTree && in_array($this->ID, Director::get_current_page()->getAncestors()->column())
465 );
466 }
467
468 469 470 471 472
473 public function LinkOrCurrent() {
474 return $this->isCurrent() ? 'current' : 'link';
475 }
476
477 478 479 480 481
482 public function LinkOrSection() {
483 return $this->isSection() ? 'section' : 'link';
484 }
485
486 487 488 489 490 491
492 public function LinkingMode() {
493 if($this->isCurrent()) {
494 return 'current';
495 } elseif($this->isSection()) {
496 return 'section';
497 } else {
498 return 'link';
499 }
500 }
501
502 503 504 505 506 507
508 public function InSection($sectionName) {
509 $page = Director::get_current_page();
510 while($page) {
511 if($sectionName == $page->URLSegment)
512 return true;
513 $page = $page->Parent;
514 }
515 return false;
516 }
517
518
519 520 521 522 523 524
525 public function () {
526 $spamfilter = isset($_GET['showspam']) ? '' : "AND \"IsSpam\"=0";
527 $unmoderatedfilter = Permission::check('ADMIN') ? '' : "AND \"NeedsModeration\"=0";
528 $comments = DataObject::get("PageComment", "\"ParentID\" = '" . Convert::raw2sql($this->ID) . "' $spamfilter $unmoderatedfilter", "\"Created\" DESC");
529
530 return $comments ? $comments : new DataObjectSet();
531 }
532
533 534 535 536
537 function (){
538 return true;
539 }
540 541 542 543 544 545
546 public function duplicate($doWrite = true) {
547
548 $page = parent::duplicate(false);
549 $page->Sort = 0;
550 $this->extend('onBeforeDuplicate', $page);
551
552 if($doWrite) {
553 $page->write();
554 }
555 $this->extend('onAfterDuplicate', $page);
556
557 return $page;
558 }
559
560
561 562 563 564 565 566
567 public function duplicateWithChildren() {
568 $clone = $this->duplicate();
569 $children = $this->AllChildren(2);
570
571 if($children) {
572 foreach($children as $child) {
573 $childClone = method_exists($child, 'duplicateWithChildren')
574 ? $child->duplicateWithChildren()
575 : $child->duplicate();
576 $childClone->ParentID = $clone->ID;
577 $childClone->write();
578 }
579 }
580
581 return $clone;
582 }
583
584
585 586 587 588 589 590
591 public function duplicateAsChild($id) {
592 $newSiteTree = $this->duplicate();
593 $newSiteTree->ParentID = $id;
594 $newSiteTree->Sort = 0;
595 $newSiteTree->write();
596 }
597
598 599 600 601 602 603 604 605 606 607
608 public function Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false) {
609 $page = $this;
610 $parts = array();
611 $i = 0;
612 while(
613 $page
614 && (!$maxDepth || sizeof($parts) < $maxDepth)
615 && (!$stopAtPageType || $page->ClassName != $stopAtPageType)
616 ) {
617 if($showHidden || $page->ShowInMenus || ($page->ID == $this->ID)) {
618 if($page->URLSegment == 'home') $hasHome = true;
619 if(($page->ID == $this->ID) || $unlinked) {
620 $parts[] = Convert::raw2xml($page->Title);
621 } else {
622 $parts[] = ("<a href=\"" . $page->Link() . "\">" . Convert::raw2xml($page->Title) . "</a>");
623 }
624 }
625 $page = $page->Parent;
626 }
627
628 return implode(self::$breadcrumbs_delimiter, array_reverse($parts));
629 }
630
631 632 633 634 635 636 637 638
639 public function setParent($item) {
640 if(is_object($item)) {
641 if (!$item->exists()) $item->write();
642 $this->setField("ParentID", $item->ID);
643 } else {
644 $this->setField("ParentID", $item);
645 }
646 }
647
648 649 650 651 652
653 public function getParent() {
654 if ($this->getField("ParentID")) {
655 return DataObject::get_one("SiteTree", "\"SiteTree\".\"ID\" = " . $this->getField("ParentID"));
656 }
657 }
658
659 660 661 662 663 664 665 666
667 function NestedTitle($level = 2, $separator = " - ") {
668 $item = $this;
669 while($item && $level > 0) {
670 $parts[] = $item->Title;
671 $item = $item->Parent;
672 $level--;
673 }
674 return implode($separator, array_reverse($parts));
675 }
676
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
698 function can($perm, $member = null) {
699 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
700 $member = Member::currentUserID();
701 }
702
703 if($member && Permission::checkMember($member, "ADMIN")) return true;
704
705 if(method_exists($this, 'can' . ucfirst($perm))) {
706 $method = 'can' . ucfirst($perm);
707 return $this->$method($member);
708 }
709
710 $results = $this->extend('can', $member);
711 if($results && is_array($results)) if(!min($results)) return false;
712
713 return true;
714 }
715
716
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
733 public function canAddChildren($member = null) {
734 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
735 $member = Member::currentUserID();
736 }
737
738 if($member && Permission::checkMember($member, "ADMIN")) return $this->stat('allowed_children') != 'none';
739
740 $results = $this->extend('canAddChildren', $member);
741 if($results && is_array($results)) if(!min($results)) return false;
742
743 return $this->canEdit($member) && $this->stat('allowed_children') != 'none';
744 }
745
746
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
763 public function canView($member = null) {
764 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
765 $member = Member::currentUserID();
766 }
767
768
769 if($member && Permission::checkMember($member, array("ADMIN", "SITETREE_VIEW_ALL"))) return true;
770
771
772 $results = $this->extend('canView', $member);
773 if($results && is_array($results)) if(!min($results)) return false;
774
775
776 if(!$this->CanViewType || $this->CanViewType == 'Anyone') return true;
777
778
779 if($this->CanViewType == 'Inherit') {
780 if($this->ParentID) return $this->Parent()->canView($member);
781 else return $this->getSiteConfig()->canView($member);
782 }
783
784
785 if($this->CanViewType == 'LoggedInUsers' && $member) {
786 return true;
787 }
788
789
790 if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);
791 if(
792 $this->CanViewType == 'OnlyTheseUsers'
793 && $member
794 && $member->inGroups($this->ViewerGroups())
795 ) return true;
796
797 return false;
798 }
799
800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
817 public function canDelete($member = null) {
818 if($member instanceof Member) $memberID = $member->ID;
819 else if(is_numeric($member)) $memberID = $member;
820 else $memberID = Member::currentUserID();
821
822 if($memberID && Permission::checkMember($memberID, array("ADMIN", "SITETREE_EDIT_ALL"))) {
823 return true;
824 }
825
826
827 $results = $this->extend('canDelete', $memberID);
828 if($results && is_array($results)) if(!min($results)) return false;
829
830
831 if(isset(self::$cache_permissions['delete'][$this->ID])) {
832 return self::$cache_permissions['delete'][$this->ID];
833 }
834
835
836 $results = self::can_delete_multiple(array($this->ID), $memberID);
837
838
839
840 return isset($results[$this->ID]) ? $results[$this->ID] : false;
841 }
842
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859
860 public function canCreate($member = null) {
861 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
862 $member = Member::currentUserID();
863 }
864
865
866
867
868 $results = $this->extend('canCreate', $member);
869 if($results && is_array($results)) if(!min($results)) return false;
870
871 return $this->stat('can_create') != false || Director::isDev();
872 }
873
874
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
894 public function canEdit($member = null) {
895 if($member instanceof Member) $memberID = $member->ID;
896 else if(is_numeric($member)) $memberID = $member;
897 else $memberID = Member::currentUserID();
898
899 if($memberID && Permission::checkMember($memberID, array("ADMIN", "SITETREE_EDIT_ALL"))) return true;
900
901
902 $results = $this->extend('canEdit', $memberID);
903 if($results && is_array($results)) if(!min($results)) return false;
904
905 if($this->ID) {
906
907 if(isset(self::$cache_permissions['CanEditType'][$this->ID])) {
908 return self::$cache_permissions['CanEditType'][$this->ID];
909 }
910
911
912 $results = self::can_edit_multiple(array($this->ID), $memberID);
913
914
915
916 return isset($results[$this->ID]) ? $results[$this->ID] : false;
917
918
919 } else {
920 return $this->getSiteConfig()->canEdit($member);
921 }
922 }
923
924 925 926 927 928 929 930 931 932 933 934 935 936 937
938 public function canPublish($member = null) {
939 if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
940
941 if($member && Permission::checkMember($member, "ADMIN")) return true;
942
943
944
945 $results = $this->extend('canPublish', $member);
946 if($results && is_array($results)) if(!min($results)) return false;
947
948
949 return $this->canEdit($member);
950 }
951
952 public function canDeleteFromLive($member = null) {
953
954
955 $results = $this->extend('canDeleteFromLive', $member);
956 if($results && is_array($results)) if(!min($results)) return false;
957
958 return $this->canPublish($member);
959 }
960
961 962 963
964 function getSiteConfig() {
965 $altConfig = false;
966 if($this->hasMethod('alternateSiteConfig')) {
967 $altConfig = $this->alternateSiteConfig();
968 }
969 if($altConfig) {
970 return $altConfig;
971 } elseif($this->hasExtension('Translatable')) {
972 return SiteConfig::current_site_config($this->Locale);
973 } else {
974 return SiteConfig::current_site_config();
975 }
976 }
977
978
979 980 981 982 983 984 985 986 987
988 static function ($permission = 'CanEditType', $ids, $batchCallback = null) {
989 if(!$batchCallback) $batchCallback = "SiteTree::can_{$permission}_multiple";
990
991
992 $batchCallback=explode('::', $batchCallback);
993
994 if(is_callable($batchCallback)) {
995 $permissionValues = call_user_func($batchCallback, $ids,
996 Member::currentUserID(), false);
997
998 if(!isset(self::$cache_permissions[$permission])) {
999 self::$cache_permissions[$permission] = array();
1000 }
1001
1002 self::$cache_permissions[$permission] = $permissionValues
1003 + self::$cache_permissions[$permission];
1004
1005 } else {
1006 user_error("SiteTree::prepopuplate_permission_cache can't calculate '$permission' "
1007 . "with callback '$batchCallback'", E_USER_WARNING);
1008 }
1009 }
1010
1011 static function batch_permission_check($ids, $memberID, $typeField, $groupJoinTable, $siteConfigMethod, $globalPermission = 'CMS_ACCESS_CMSMain', $useCached = true) {
1012
1013 $ids = array_filter($ids, 'is_numeric');
1014
1015
1016
1017 $cacheKey = strtolower(substr($typeField, 3, -4));
1018
1019
1020 $result = array_fill_keys($ids, false);
1021 if($ids) {
1022
1023
1024 if($useCached && isset(self::$cache_permissions[$cacheKey])) {
1025 $cachedValues = array_intersect_key(self::$cache_permissions[$cacheKey], $result);
1026
1027
1028 $uncachedValues = array_diff_key($result, self::$cache_permissions[$cacheKey]);
1029 if($uncachedValues) {
1030 $cachedValues = self::batch_permission_check(array_keys($uncachedValues), $memberID, $typeField, $groupJoinTable, $siteConfigMethod, $globalPermission, false) + $cachedValues;
1031 }
1032 return $cachedValues;
1033 }
1034
1035
1036 if(!$memberID || ($globalPermission && !Permission::checkMember($memberID, $globalPermission))) {
1037 return $result;
1038 }
1039
1040 $SQL_idList = implode($ids, ", ");
1041
1042
1043
1044
1045
1046
1047 $groupIDs = DataObject::get_by_id('Member', $memberID)->Groups()->column("ID");
1048 $SQL_groupList = implode(", ", $groupIDs);
1049 if (!$SQL_groupList) $SQL_groupList = '0';
1050
1051 $combinedStageResult = array();
1052
1053 foreach(array('Stage', 'Live') as $stage) {
1054
1055 $table = ($stage=='Stage') ? "SiteTree" : "SiteTree_$stage";
1056
1057 $result = array_fill_keys(DB::query("SELECT \"ID\" FROM \"$table\"
1058 WHERE \"ID\" IN (".implode(", ", $ids).")")->column(), false);
1059
1060
1061 $uninheritedPermissions = Versioned::get_by_stage("SiteTree", $stage, "(\"$typeField\" = 'LoggedInUsers' OR
1062 (\"$typeField\" = 'OnlyTheseUsers' AND \"$groupJoinTable\".\"SiteTreeID\" IS NOT NULL))
1063 AND \"SiteTree\".\"ID\" IN ($SQL_idList)",
1064 "",
1065 "LEFT JOIN \"$groupJoinTable\"
1066 ON \"$groupJoinTable\".\"SiteTreeID\" = \"SiteTree\".\"ID\"
1067 AND \"$groupJoinTable\".\"GroupID\" IN ($SQL_groupList)");
1068
1069 if($uninheritedPermissions) {
1070
1071 $result = array_fill_keys($uninheritedPermissions->column('ID'), true) + $result;
1072 }
1073
1074
1075 $potentiallyInherited = Versioned::get_by_stage("SiteTree", $stage, "\"$typeField\" = 'Inherit'
1076 AND \"SiteTree\".\"ID\" IN ($SQL_idList)");
1077
1078 if($potentiallyInherited) {
1079
1080
1081 $siteConfigPermission = SiteConfig::current_site_config()->{$siteConfigMethod}($memberID);
1082 $groupedByParent = array();
1083 foreach($potentiallyInherited as $item) {
1084 if($item->ParentID) {
1085 if(!isset($groupedByParent[$item->ParentID])) $groupedByParent[$item->ParentID] = array();
1086 $groupedByParent[$item->ParentID][] = $item->ID;
1087 } else {
1088 $result[$item->ID] = $siteConfigPermission;
1089 }
1090 }
1091
1092 if($groupedByParent) {
1093 $actuallyInherited = self::batch_permission_check(array_keys($groupedByParent), $memberID, $typeField, $groupJoinTable, $siteConfigMethod);
1094 if($actuallyInherited) {
1095 $parentIDs = array_keys(array_filter($actuallyInherited));
1096 foreach($parentIDs as $parentID) {
1097
1098 $result = array_fill_keys($groupedByParent[$parentID], true) + $result;
1099 }
1100 }
1101 }
1102 }
1103
1104 $combinedStageResult = $combinedStageResult + $result;
1105
1106 }
1107 }
1108
1109 if(isset($combinedStageResult)) {
1110
1111
1112
1113
1114 1115 1116
1117 return $combinedStageResult;
1118 } else {
1119 return array();
1120 }
1121 }
1122
1123 1124 1125 1126 1127 1128 1129 1130
1131 static function can_edit_multiple($ids, $memberID, $useCached = true) {
1132 return self::batch_permission_check($ids, $memberID, 'CanEditType', 'SiteTree_EditorGroups', 'canEdit', 'CMS_ACCESS_CMSMain', $useCached);
1133 }
1134
1135 1136 1137 1138 1139
1140 static function can_delete_multiple($ids, $memberID, $useCached = true) {
1141 $deletable = array();
1142
1143 $result = array_fill_keys($ids, false);
1144
1145
1146 if($useCached && isset(self::$cache_permissions['delete'])) {
1147 $cachedValues = array_intersect_key(self::$cache_permissions['delete'], $result);
1148
1149
1150 $uncachedValues = array_diff_key($result, self::$cache_permissions['delete']);
1151 if($uncachedValues) {
1152 $cachedValues = self::can_delete_multiple(array_keys($uncachedValues), $memberID, false)
1153 + $cachedValues;
1154 }
1155 return $cachedValues;
1156 }
1157
1158
1159 $editableIDs = array_keys(array_filter(self::can_edit_multiple($ids, $memberID)));
1160 if($editableIDs) {
1161 $idList = implode(",", $editableIDs);
1162
1163
1164 $childRecords = DataObject::get("SiteTree", "\"ParentID\" IN ($idList)");
1165 if($childRecords) {
1166 $children = $childRecords->map("ID", "ParentID");
1167
1168
1169 $deletableChildren = self::can_delete_multiple(array_keys($children), $memberID);
1170
1171
1172 $deletableParents = array_fill_keys($editableIDs, true);
1173 foreach($deletableChildren as $id => $canDelete) {
1174 if(!$canDelete) unset($deletableParents[$children[$id]]);
1175 }
1176
1177
1178 $deletableParents = array_keys($deletableParents);
1179
1180
1181 $parents = array_unique($children);
1182 $deletableLeafNodes = array_diff($editableIDs, $parents);
1183
1184
1185 $deletable = array_merge($deletableParents, $deletableLeafNodes);
1186
1187 } else {
1188 $deletable = $editableIDs;
1189 }
1190 } else {
1191 $deletable = array();
1192 }
1193
1194
1195
1196 return array_fill_keys($deletable, true) + array_fill_keys($ids, false);
1197 }
1198
1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
1210 public function collateDescendants($condition, &$collator) {
1211 if($children = $this->Children()) {
1212 foreach($children as $item) {
1213 if(eval("return $condition;")) $collator[] = $item;
1214 $item->collateDescendants($condition, $collator);
1215 }
1216 return true;
1217 }
1218 }
1219
1220
1221 1222 1223 1224 1225 1226 1227 1228 1229 1230
1231 public function MetaTags($includeTitle = true) {
1232 $tags = "";
1233 if($includeTitle === true || $includeTitle == 'true') {
1234 $tags .= "<title>" . Convert::raw2xml(($this->MetaTitle)
1235 ? $this->MetaTitle
1236 : $this->Title) . "</title>\n";
1237 }
1238
1239 $charset = ContentNegotiator::get_encoding();
1240 $tags .= "<meta http-equiv=\"Content-type\" content=\"text/html; charset=$charset\" />\n";
1241 if($this->MetaKeywords) {
1242 $tags .= "<meta name=\"keywords\" content=\"" . Convert::raw2att($this->MetaKeywords) . "\" />\n";
1243 }
1244 if($this->MetaDescription) {
1245 $tags .= "<meta name=\"description\" content=\"" . Convert::raw2att($this->MetaDescription) . "\" />\n";
1246 }
1247 if($this->ExtraMeta) {
1248 $tags .= $this->ExtraMeta . "\n";
1249 }
1250
1251 $this->extend('MetaTags', $tags);
1252
1253 return $tags;
1254 }
1255
1256
1257 1258 1259 1260 1261 1262 1263 1264 1265 1266
1267 public function ContentSource() {
1268 return $this;
1269 }
1270
1271
1272 1273 1274 1275 1276 1277 1278 1279
1280 function requireDefaultRecords() {
1281 parent::requireDefaultRecords();
1282
1283
1284 if($this->class == 'SiteTree') {
1285 if(!SiteTree::get_by_link('home')) {
1286 $homepage = new Page();
1287 $homepage->Title = _t('SiteTree.DEFAULTHOMETITLE', 'Home');
1288 $homepage->Content = _t('SiteTree.DEFAULTHOMECONTENT', '<p>Welcome to SilverStripe! This is the default homepage. You can edit this page by opening <a href="admin/">the CMS</a>. You can now access the <a href="http://doc.silverstripe.org">developer documentation</a>, or begin <a href="http://doc.silverstripe.org/doku.php?id=tutorials">the tutorials.</a></p>');
1289 $homepage->URLSegment = 'home';
1290 $homepage->Status = 'Published';
1291 $homepage->Sort = 1;
1292 $homepage->write();
1293 $homepage->publish('Stage', 'Live');
1294 $homepage->flushCache();
1295 DB::alteration_message('Home page created', 'created');
1296 }
1297
1298 if(DB::query("SELECT COUNT(*) FROM \"SiteTree\"")->value() == 1) {
1299 $aboutus = new Page();
1300 $aboutus->Title = _t('SiteTree.DEFAULTABOUTTITLE', 'About Us');
1301 $aboutus->Content = _t('SiteTree.DEFAULTABOUTCONTENT', '<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>');
1302 $aboutus->Status = 'Published';
1303 $aboutus->URLSegment = 'about';
1304 $aboutus->Sort = 2;
1305 $aboutus->write();
1306 $aboutus->publish('Stage', 'Live');
1307 $aboutus->flushCache();
1308 DB::alteration_message('About Us page created', 'created');
1309
1310 $contactus = new Page();
1311 $contactus->Title = _t('SiteTree.DEFAULTCONTACTTITLE', 'Contact Us');
1312 $contactus->Content = _t('SiteTree.DEFAULTCONTACTCONTENT', '<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>');
1313 $contactus->Status = 'Published';
1314 $contactus->URLSegment = 'contacts';
1315 $contactus->Sort = 3;
1316 $contactus->write();
1317 $contactus->publish('Stage', 'Live');
1318 $contactus->flushCache();
1319 DB::alteration_message('Contact Us page created', 'created');
1320 }
1321 }
1322
1323
1324
1325 if($this->class == 'SiteTree') {
1326 $conn = DB::getConn();
1327
1328 if(array_key_exists('Viewers', $conn->fieldList('SiteTree'))) {
1329 $task = new UpgradeSiteTreePermissionSchemaTask();
1330 $task->run(new SS_HTTPRequest('GET','/'));
1331 }
1332 }
1333 }
1334
1335
1336
1337
1338 protected function onBeforeWrite() {
1339 parent::onBeforeWrite();
1340
1341
1342 if(!$this->Sort) {
1343 $parentID = ($this->ParentID) ? $this->ParentID : 0;
1344 $this->Sort = DB::query("SELECT MAX(\"Sort\") + 1 FROM \"SiteTree\" WHERE \"ParentID\" = $parentID")->value();
1345 }
1346
1347
1348 if((!$this->URLSegment || $this->URLSegment == 'new-page') && $this->Title) {
1349 $this->URLSegment = $this->generateURLSegment($this->Title);
1350 } else if($this->isChanged('URLSegment')) {
1351
1352 $segment = ereg_replace('[^A-Za-z0-9_-]+','-',Convert::rus2lat($this->URLSegment));
1353 $segment = ereg_replace('-+','-',$segment);
1354 $segment = ereg_replace('_+','_',$segment);
1355 $segment = ereg_replace('^[-_]+','',$segment);
1356 $segment = ereg_replace('[-_]+$','',$segment);
1357
1358
1359 if(!$segment) {
1360 $segment = "page-$this->ID";
1361 }
1362 $this->URLSegment = $segment;
1363 }
1364
1365 DataObject::set_context_obj($this);
1366
1367
1368 $count = 2;
1369 while(!$this->validURLSegment()) {
1370 $this->URLSegment = preg_replace('/-[0-9]+$/', null, $this->URLSegment) . '-' . $count;
1371 $count++;
1372 }
1373
1374 DataObject::set_context_obj(null);
1375
1376 $this->syncLinkTracking();
1377
1378
1379 $fieldsIgnoredByVersioning = array('HasBrokenLink', 'Status', 'HasBrokenFile', 'ToDo');
1380 $changedFields = array_keys($this->getChangedFields(true, 2));
1381
1382
1383
1384
1385
1386 $oneChangedFields = array_keys($this->getChangedFields(true, 1));
1387
1388 if($oneChangedFields && !array_diff($changedFields, $fieldsIgnoredByVersioning)) {
1389
1390 $this->migrateVersion($this->Version);
1391 }
1392 }
1393
1394 function syncLinkTracking() {
1395
1396 $allFields = $this->db();
1397 $htmlFields = array();
1398 foreach($allFields as $field => $fieldSpec) {
1399 if ($field == 'ExtraMeta') continue;
1400 if(preg_match('/([^(]+)/', $fieldSpec, $matches)) {
1401 $class = $matches[0];
1402 if(class_exists($class)){
1403 if($class == 'HTMLText' || is_subclass_of($class, 'HTMLText')) $htmlFields[] = $field;
1404 }
1405 }
1406 }
1407
1408 $linkedPages = array();
1409 $linkedFiles = array();
1410 $this->HasBrokenLink = false;
1411 $this->HasBrokenFile = false;
1412
1413 foreach($htmlFields as $field) {
1414 $formField = new HTMLEditorField($field);
1415 $formField->setValue($this->$field);
1416 $formField->saveInto($this);
1417 }
1418
1419
1420 if ($this->ID) {
1421 if (($has_one = $this->has_one()) && count($has_one)) {
1422 foreach($has_one as $name => $type) {
1423 if (singleton($type)->is_a('File') && !singleton($type)->is_a('Folder') && $this->{"{$name}ID"}) {
1424 if (!DataObject::get_by_id($type, $this->{"{$name}ID"})) {
1425 $this->HasBrokenFile = true;
1426 }
1427 }
1428 }
1429 }
1430 }
1431 $this->extend('augmentSyncLinkTracking');
1432 }
1433
1434 function onAfterWrite() {
1435
1436 $this->flushCache();
1437
1438
1439 $linkedPages = $this->VirtualPages();
1440 if($linkedPages) foreach($linkedPages as $page) {
1441 $page->copyFrom($page->CopyContentFrom());
1442 $page->write();
1443 }
1444
1445 parent::onAfterWrite();
1446 }
1447
1448 function onBeforeDelete() {
1449 parent::onBeforeDelete();
1450
1451
1452 if(SiteTree::get_enforce_strict_hierarchy() && $children = $this->AllChildren(2)) {
1453 foreach($children as $child) {
1454 $child->delete();
1455 }
1456 }
1457 }
1458
1459
1460 function onAfterDelete() {
1461
1462 $this->flushCache();
1463
1464
1465 $dependentPages = $this->DependentPages();
1466 if($dependentPages) foreach($dependentPages as $page) {
1467
1468 $page->write();
1469 }
1470
1471 parent::onAfterDelete();
1472 }
1473
1474 1475 1476 1477 1478 1479 1480 1481 1482
1483 public function validURLSegment() {
1484 if(self::nested_urls() && $parent = $this->Parent()) {
1485 if($controller = ModelAsController::controller_for($parent)) {
1486 if($controller instanceof Controller && $controller->hasAction($this->URLSegment)) return false;
1487 }
1488 }
1489
1490 if(!self::nested_urls() || !$this->ParentID) {
1491 if(class_exists($this->URLSegment) && is_subclass_of($this->URLSegment, 'RequestHandler')) return false;
1492 }
1493
1494 $IDFilter = ($this->ID) ? "AND \"SiteTree\".\"ID\" <> $this->ID" : null;
1495 $parentFilter = null;
1496
1497 if(self::nested_urls()) {
1498 if($this->ParentID) {
1499 $parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID";
1500 } else {
1501 $parentFilter = ' AND "SiteTree"."ParentID" = 0';
1502 }
1503 }
1504
1505 $existingPage = DataObject::get_one(
1506 'SiteTree',
1507 "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter"
1508 );
1509 if ($existingPage) {
1510 return false;
1511 }
1512
1513 $values = $this->extend('augmentValidURLSegment');
1514 if (count($values) && !min($values)) {
1515 return false;
1516 }
1517
1518 return true;
1519 }
1520
1521 1522 1523 1524 1525
1526 function generateURLSegment($title){
1527 $t = strtolower($title);
1528 $t = Convert::rus2lat($t);
1529 $t = str_replace('&','-and-',$t);
1530 $t = str_replace('&','-and-',$t);
1531 $t = ereg_replace('[^A-Za-z0-9_-]+','-',$t);
1532 $t = ereg_replace('-+','-',$t);
1533 $t = ereg_replace('_+','_',$t);
1534 $t = ereg_replace('^[-_]+','',$t);
1535 $t = ereg_replace('[-_]+$','',$t);
1536
1537 if(!$t || $t == '-' || $t == '-1') {
1538 $t = "page-$this->ID";
1539 }
1540 return trim($t, '-');
1541 }
1542
1543 1544 1545 1546
1547 function rewriteFileURL($old, $new) {
1548 $fields = $this->inheritedDatabaseFields();
1549
1550 foreach(array("SiteTree_Live", "SiteTree") as $table) {
1551
1552 $published = DB::query("SELECT * FROM \"$table\" WHERE \"ID\" = $this->ID")->record();
1553 $origPublished = $published;
1554
1555 foreach($fields as $fieldName => $fieldType) {
1556 if ($fieldType != 'HTMLText') continue;
1557
1558
1559 if(isset($published[$fieldName])) {
1560
1561 $oldFileMask = '!' . dirname($old) . '/(_resampled/resizedimage[0-9]+-)?' . basename($old) . '!';
1562 $published[$fieldName] = preg_replace($oldFileMask, $new, $published[$fieldName], -1, $numReplaced);
1563 if($numReplaced) {
1564 DB::query("UPDATE \"$table\" SET \"$fieldName\" = '"
1565 . Convert::raw2sql($published[$fieldName]) . "' WHERE \"ID\" = $this->ID");
1566
1567
1568 if($table == 'SiteTree_Live') {
1569 $publishedClass = $origPublished['ClassName'];
1570 $origPublishedObj = new $publishedClass($origPublished);
1571 $this->extend('onRenameLinkedAsset', $origPublishedObj);
1572 }
1573 }
1574 }
1575 }
1576 }
1577 }
1578
1579 1580 1581 1582 1583 1584
1585 function DependentPages($includeVirtuals = true) {
1586 if(is_callable('Subsite::disable_subsite_filter')) Subsite::disable_subsite_filter(true);
1587
1588
1589 $items = $this->BackLinkTracking();
1590 if(!$items) $items = new DataObjectSet();
1591 else foreach($items as $item) $item->DependentLinkType = 'Content link';
1592
1593
1594 if($includeVirtuals) {
1595 $virtuals = $this->VirtualPages();
1596 if($virtuals) {
1597 foreach($virtuals as $item) $item->DependentLinkType = 'Virtual page';
1598 $items->merge($virtuals);
1599 }
1600 }
1601
1602
1603 $redirectors = DataObject::get("RedirectorPage", "\"RedirectionType\" = 'Internal' AND \"LinkToID\" = $this->ID");
1604 if($redirectors) {
1605 foreach($redirectors as $item) $item->DependentLinkType = 'Redirector page';
1606 $items->merge($redirectors);
1607 }
1608
1609 if(is_callable('Subsite::disable_subsite_filter')) Subsite::disable_subsite_filter(false);
1610 return $items;
1611 }
1612
1613 1614 1615 1616 1617
1618 function DependentPagesCount($includeVirtuals = true) {
1619 $links = DB::query("SELECT COUNT(*) FROM \"SiteTree_LinkTracking\"
1620 INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"SiteTree_LinkTracking\".\"SiteTreeID\"
1621 WHERE \"ChildID\" = $this->ID ")->value();
1622 if($includeVirtuals) {
1623 $virtuals = DB::query("SELECT COUNT(*) FROM \"VirtualPage\"
1624 INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"VirtualPage\".\"ID\"
1625 WHERE \"CopyContentFromID\" = $this->ID")->value();
1626 } else {
1627 $virtuals = 0;
1628 }
1629 $redirectors = DB::query("SELECT COUNT(*) FROM \"RedirectorPage\"
1630 INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"RedirectorPage\".\"ID\"
1631 WHERE \"RedirectionType\" = 'Internal' AND \"LinkToID\" = $this->ID")->value();
1632
1633
1634 return 0 + $links + $virtuals + $redirectors;
1635 }
1636
1637 1638 1639
1640 function VirtualPages() {
1641 if(!$this->ID) return null;
1642 if(class_exists('Subsite')) {
1643 return Subsite::get_from_all_subsites('VirtualPage', "\"CopyContentFromID\" = " . (int)$this->ID);
1644 } else {
1645 return DataObject::get('VirtualPage', "\"CopyContentFromID\" = " . (int)$this->ID);
1646 }
1647 }
1648
1649 1650 1651 1652 1653 1654 1655 1656 1657
1658 function getCMSFields() {
1659 require_once("forms/Form.php");
1660 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
1661 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
1662 Requirements::javascript(CMS_DIR . "/javascript/SitetreeAccess.js");
1663 Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
1664 Requirements::javascript(SAPPHIRE_DIR . '/javascript/UpdateURL.js');
1665
1666
1667
1668 if($this->ID && is_numeric($this->ID)) {
1669 $linkedPages = $this->VirtualPages();
1670 }
1671
1672 $parentPageLinks = array();
1673
1674 if(isset($linkedPages)) {
1675 foreach($linkedPages as $linkedPage) {
1676 $parentPage = $linkedPage->Parent;
1677 if($parentPage) {
1678 if($parentPage->ID) {
1679 $parentPageLinks[] = "<a class=\"cmsEditlink\" href=\"admin/show/$linkedPage->ID\">{$parentPage->Title}</a>";
1680 } else {
1681 $parentPageLinks[] = "<a class=\"cmsEditlink\" href=\"admin/show/$linkedPage->ID\">" .
1682 _t('SiteTree.TOPLEVEL', 'Site Content (Top Level)') .
1683 "</a>";
1684 }
1685 }
1686 }
1687
1688 $lastParent = array_pop($parentPageLinks);
1689 $parentList = "'$lastParent'";
1690
1691 if(count($parentPageLinks) > 0) {
1692 $parentList = "'" . implode("', '", $parentPageLinks) . "' and "
1693 . $parentList;
1694 }
1695
1696 $statusMessage[] = sprintf(
1697 _t('SiteTree.APPEARSVIRTUALPAGES', "This content also appears on the virtual pages in the %s sections."),
1698 $parentList
1699 );
1700 }
1701
1702 if($this->HasBrokenLink || $this->HasBrokenFile) {
1703 $statusMessage[] = _t('SiteTree.HASBROKENLINKS', "This page has broken links.");
1704 }
1705
1706 $message = "STATUS: $this->Status<br />";
1707 if(isset($statusMessage)) {
1708 $message .= "NOTE: " . implode("<br />", $statusMessage);
1709 }
1710
1711 $dependentNote = '';
1712 $dependentTable = new LiteralField('DependentNote', '<p>'._t('SiteTree.DEPENDENTNOTE','No dependent pages').'</p>');
1713
1714
1715 $dependentPagesCount = $this->DependentPagesCount();
1716 if($dependentPagesCount) {
1717 $dependentColumns = array(
1718 'Title' => $this->fieldLabel('Title'),
1719 'AbsoluteLink' => _t('SiteTree.DependtPageColumnURL', 'URL'),
1720 'DependentLinkType' => _t('SiteTree.DependtPageColumnLinkType', 'Link type'),
1721 );
1722 if(class_exists('Subsite')) $dependentColumns['Subsite.Title'] = singleton('Subsite')->i18n_singular_name();
1723
1724 $dependentNote = new LiteralField('DependentNote', '<p>' . _t('SiteTree.DEPENDENT_NOTE', 'The following pages depend on this page. This includes virtual pages, redirector pages, and pages with content links.') . '</p>');
1725 $dependentTable = new TableListField(
1726 'DependentPages',
1727 'SiteTree',
1728 $dependentColumns
1729 );
1730 $dependentTable->setCustomSourceItems($this->DependentPages());
1731 $dependentTable->setFieldFormatting(array(
1732 'Title' => '<a href=\"admin/show/$ID\">$Title</a>',
1733 'AbsoluteLink' => '<a href=\"$value\">$value</a>',
1734 ));
1735 $dependentTable->setPermissions(array(
1736 'show',
1737 'export'
1738 ));
1739 }
1740
1741
1742 $fields = new FieldSet(
1743 $rootTab = new TabSet("Root",
1744 $tabContent = new TabSet('Content',
1745 $tabMain = new Tab('Main',
1746 new TextField("Title", $this->fieldLabel('Title')),
1747 new TextField("MenuTitle", $this->fieldLabel('MenuTitle')),
1748 new HtmlEditorField("Content", _t('SiteTree.HTMLEDITORTITLE', "Content", PR_MEDIUM, 'HTML editor title'))
1749 ),
1750 $tabMeta = new Tab('Metadata',
1751 new FieldGroup(_t('SiteTree.URL', "URL"),
1752 new LabelField('BaseUrlLabel',Controller::join_links (
1753 Director::absoluteBaseURL(),
1754 (self::nested_urls() && $this->ParentID ? $this->Parent()->RelativeLink(true) : null)
1755 )),
1756 new UniqueRestrictedTextField("URLSegment",
1757 "URLSegment",
1758 "SiteTree",
1759 _t('SiteTree.VALIDATIONURLSEGMENT1', "Another page is using that URL. URL must be unique for each page"),
1760 "[^A-Za-z0-9_-]+",
1761 "-",
1762 _t('SiteTree.VALIDATIONURLSEGMENT2', "URLs can only be made up of letters, digits and hyphens."),
1763 "",
1764 "",
1765 250
1766 ),
1767 new LabelField('TrailingSlashLabel',"/")
1768 ),
1769 new LiteralField('LinkChangeNote', self::nested_urls() && count($this->AllChildren()) ?
1770 '<p>' . $this->fieldLabel('LinkChangeNote'). '</p>' : null
1771 ),
1772 new HeaderField('MetaTagsHeader',$this->fieldLabel('MetaTagsHeader')),
1773 new TextField("MetaTitle", $this->fieldLabel('MetaTitle')),
1774 new TextareaField("MetaKeywords", $this->fieldLabel('MetaKeywords'), 1),
1775 new TextareaField("MetaDescription", $this->fieldLabel('MetaDescription')),
1776 new TextareaField("ExtraMeta",$this->fieldLabel('ExtraMeta'))
1777 )
1778 ),
1779 $tabBehaviour = new Tab('Behaviour',
1780 new DropdownField(
1781 "ClassName",
1782 $this->fieldLabel('ClassName'),
1783 $this->getClassDropdown()
1784 ),
1785
1786 new OptionsetField("ParentType", _t("SiteTree.PAGELOCATION", "Page location"), array(
1787 "root" => _t("SiteTree.PARENTTYPE_ROOT", "Top-level page"),
1788 "subpage" => _t("SiteTree.PARENTTYPE_SUBPAGE", "Sub-page underneath a parent page (choose below)"),
1789 )),
1790 $parentIDField = new TreeDropdownField("ParentID", $this->fieldLabel('ParentID'), 'SiteTree'),
1791
1792 new CheckboxField("ShowInMenus", $this->fieldLabel('ShowInMenus')),
1793 new CheckboxField("ShowInSearch", $this->fieldLabel('ShowInSearch')),
1794
1795 new CheckboxField("ProvideComments", $this->fieldLabel('ProvideComments')),
1796 new LiteralField(
1797 "HomepageForDomainInfo",
1798 "<p>" .
1799 _t('SiteTree.NOTEUSEASHOMEPAGE',
1800 "Use this page as the 'home page' for the following domains:
1801 (separate multiple domains with commas)") .
1802 "</p>"
1803 ),
1804 new TextField(
1805 "HomepageForDomain",
1806 _t('SiteTree.HOMEPAGEFORDOMAIN', "Domain(s)", PR_MEDIUM, 'Listing domains that should be used as homepage')
1807 ),
1808 new NumericField(
1809 "NumberCMSChildren",
1810 _t('SiteTree.NUMBERCMSCHILDREN',"Total displayed subpages in CMS (must be numeric, defaults to 0 == all, page refresh required after changing this)"),
1811 $this->NumberCMSChildren
1812 )
1813
1814 ),
1815 $tabToDo = new Tab('Todo',
1816 new LiteralField("ToDoHelp", _t('SiteTree.TODOHELP', "<p>You can use this to keep track of work that needs to be done to the content of your site. To see all your pages with to do information, open the 'Site Reports' window on the left and select 'To Do'</p>")),
1817 new TextareaField("ToDo", "")
1818 ),
1819 $tabDependent = new Tab('Dependent',
1820 $dependentNote,
1821 $dependentTable
1822 ),
1823 $tabAccess = new Tab('Access',
1824 new HeaderField('WhoCanViewHeader',_t('SiteTree.ACCESSHEADER', "Who can view this page?"), 2),
1825 $viewersOptionsField = new OptionsetField(
1826 "CanViewType",
1827 ""
1828 ),
1829 $viewerGroupsField = new TreeMultiselectField("ViewerGroups", $this->fieldLabel('ViewerGroups')),
1830 new HeaderField('WhoCanEditHeader',_t('SiteTree.EDITHEADER', "Who can edit this page?"), 2),
1831 $editorsOptionsField = new OptionsetField(
1832 "CanEditType",
1833 ""
1834 ),
1835 $editorGroupsField = new TreeMultiselectField("EditorGroups", $this->fieldLabel('EditorGroups'))
1836 )
1837 )
1838
1839 );
1840
1841 if ($this->stat('allowed_children') == 'none')
1842 $fields->removeByName("NumberCMSChildren");
1843
1844
1845
1846 1847 1848 1849
1850 $parentIDField->setFilterFunction(create_function('$node', "return \$node->ID != {$this->ID};"));
1851
1852
1853 if($dependentPagesCount) $tabDependent->setTitle(_t('SiteTree.TABDEPENDENT', "Dependent pages") . " ($dependentPagesCount)");
1854 else $fields->removeFieldFromTab('Root', 'Dependent');
1855
1856
1857 if(!Permission::check("SITETREE_REORGANISE")) {
1858 $fields->makeFieldReadonly('ParentType');
1859 if($this->ParentType == 'root') {
1860 $fields->removeByName('ParentID');
1861 } else {
1862 $fields->makeFieldReadonly('ParentID');
1863 }
1864 }
1865
1866 $viewersOptionsSource = array();
1867 $viewersOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page");
1868 $viewersOptionsSource["Anyone"] = _t('SiteTree.ACCESSANYONE', "Anyone");
1869 $viewersOptionsSource["LoggedInUsers"] = _t('SiteTree.ACCESSLOGGEDIN', "Logged-in users");
1870 $viewersOptionsSource["OnlyTheseUsers"] = _t('SiteTree.ACCESSONLYTHESE', "Only these people (choose from list)");
1871 $viewersOptionsField->setSource($viewersOptionsSource);
1872
1873 $editorsOptionsSource = array();
1874 $editorsOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page");
1875 $editorsOptionsSource["LoggedInUsers"] = _t('SiteTree.EDITANYONE', "Anyone who can log-in to the CMS");
1876 $editorsOptionsSource["OnlyTheseUsers"] = _t('SiteTree.EDITONLYTHESE', "Only these people (choose from list)");
1877 $editorsOptionsField->setSource($editorsOptionsSource);
1878
1879 if(!Permission::check('SITETREE_GRANT_ACCESS')) {
1880 $fields->makeFieldReadonly($viewersOptionsField);
1881 if($this->CanViewType == 'OnlyTheseUsers') {
1882 $fields->makeFieldReadonly($viewerGroupsField);
1883 } else {
1884 $fields->removeByName('ViewerGroups');
1885 }
1886
1887 $fields->makeFieldReadonly($editorsOptionsField);
1888 if($this->CanEditType == 'OnlyTheseUsers') {
1889 $fields->makeFieldReadonly($editorGroupsField);
1890 } else {
1891 $fields->removeByName('EditorGroups');
1892 }
1893 }
1894
1895 $tabContent->setTitle(_t('SiteTree.TABCONTENT', "Content"));
1896 $tabMain->setTitle(_t('SiteTree.TABMAIN', "Main"));
1897 $tabMeta->setTitle(_t('SiteTree.TABMETA', "Metadata"));
1898 $tabBehaviour->setTitle(_t('SiteTree.TABBEHAVIOUR', "Behaviour"));
1899 $tabAccess->setTitle(_t('SiteTree.TABACCESS', "Access"));
1900 $tabToDo->setTitle(_t('SiteTree.TODO', "To Do") . ($this->ToDo ? ' **' : ''));
1901
1902 if(self::$runCMSFieldsExtensions) {
1903 $this->extend('updateCMSFields', $fields);
1904 }
1905
1906 return $fields;
1907 }
1908
1909 1910 1911 1912 1913
1914 function fieldLabels($includerelations = true) {
1915 $labels = parent::fieldLabels($includerelations);
1916
1917 $labels['Title'] = _t('SiteTree.PAGETITLE', "Page name");
1918 $labels['MenuTitle'] = _t('SiteTree.MENUTITLE', "Navigation label");
1919 $labels['MetaTagsHeader'] = _t('SiteTree.METAHEADER', "Search Engine Meta-tags");
1920 $labels['MetaTitle'] = _t('SiteTree.METATITLE', "Title");
1921 $labels['MetaDescription'] = _t('SiteTree.METADESC', "Description");
1922 $labels['MetaKeywords'] = _t('SiteTree.METAKEYWORDS', "Keywords");
1923 $labels['ExtraMeta'] = _t('SiteTree.METAEXTRA', "Custom Meta Tags");
1924 $labels['ClassName'] = _t('SiteTree.PAGETYPE', "Page type", PR_MEDIUM, 'Classname of a page object');
1925 $labels['ParentType'] = _t('SiteTree.PARENTTYPE', "Page location", PR_MEDIUM);
1926 $labels['ParentID'] = _t('SiteTree.PARENTID', "Parent page", PR_MEDIUM);
1927 $labels['ShowInMenus'] =_t('SiteTree.SHOWINMENUS', "Show in menus?");
1928 $labels['ShowInSearch'] = _t('SiteTree.SHOWINSEARCH', "Show in search?");
1929 $labels['ProvideComments'] = _t('SiteTree.ALLOWCOMMENTS', "Allow comments on this page?");
1930 $labels['ViewerGroups'] = _t('SiteTree.VIEWERGROUPS', "Viewer Groups");
1931 $labels['EditorGroups'] = _t('SiteTree.EDITORGROUPS', "Editor Groups");
1932 $labels['URLSegment'] = _t('SiteTree.URLSegment', 'URL Segment', PR_MEDIUM, 'URL for this page');
1933 $labels['Content'] = _t('SiteTree.Content', 'Content', PR_MEDIUM, 'Main HTML Content for a page');
1934 $labels['HomepageForDomain'] = _t('SiteTree.HomepageForDomain', 'Hompage for this domain');
1935 $labels['CanViewType'] = _t('SiteTree.Viewers', 'Viewers Groups');
1936 $labels['CanEditType'] = _t('SiteTree.Editors', 'Editors Groups');
1937 $labels['ToDo'] = _t('SiteTree.ToDo', 'Todo Notes');
1938 $labels['Comments'] = _t('SiteTree.Comments', 'Comments');
1939 $labels['LinkChangeNote'] = _t (
1940 'SiteTree.LINKCHANGENOTE', 'Changing this page\'s link will also affect the links of all child pages.'
1941 );
1942
1943 if($includerelations){
1944 $labels['Parent'] = _t('SiteTree.has_one_Parent', 'Parent Page', PR_MEDIUM, 'The parent page in the site hierarchy');
1945 $labels['LinkTracking'] = _t('SiteTree.many_many_LinkTracking', 'Link Tracking');
1946 $labels['BackLinkTracking'] = _t('SiteTree.many_many_BackLinkTracking', 'Backlink Tracking');
1947 }
1948
1949 return $labels;
1950 }
1951
1952
1953
1954 1955 1956 1957
1958 function getCMSActions() {
1959 $actions = new FieldSet();
1960
1961 if($this->isPublished() && $this->canPublish() && !$this->IsDeletedFromStage) {
1962
1963 $unpublish = FormAction::create('unpublish', _t('SiteTree.BUTTONUNPUBLISH', 'Unpublish'), 'delete');
1964 $unpublish->describe(_t('SiteTree.BUTTONUNPUBLISHDESC', "Remove this page from the published site"));
1965 $unpublish->addExtraClass('delete');
1966 $actions->push($unpublish);
1967 }
1968
1969 if($this->stagesDiffer('Stage', 'Live') && !$this->IsDeletedFromStage) {
1970 if($this->isPublished() && $this->canEdit()) {
1971
1972 $rollback = FormAction::create('rollback', _t('SiteTree.BUTTONCANCELDRAFT', 'Cancel draft changes'), 'delete');
1973 $rollback->describe(_t('SiteTree.BUTTONCANCELDRAFTDESC', "Delete your draft and revert to the currently published page"));
1974 $rollback->addExtraClass('delete');
1975 $actions->push($rollback);
1976 }
1977 }
1978
1979 if($this->canEdit()) {
1980 if($this->IsDeletedFromStage) {
1981 if($this->ExistsOnLive) {
1982
1983 $actions->push(new FormAction('revert',_t('CMSMain.RESTORE','Restore')));
1984 if($this->canDelete() && $this->canDeleteFromLive()) {
1985
1986 $actions->push(new FormAction('deletefromlive',_t('CMSMain.DELETEFP','Unpublish')));
1987 }
1988 } else {
1989
1990 $actions->push(new FormAction('restore',_t('CMSMain.RESTORE','Restore')));
1991 }
1992 } else {
1993 if($this->canDelete()) {
1994
1995 $actions->push($deleteAction = new FormAction('delete',_t('CMSMain.DELETE_DRAFT','Delete Draft')));
1996 $deleteAction->addExtraClass('delete');
1997 }
1998
1999
2000 $actions->push(new FormAction('save',_t('CMSMain.SAVE_DRAFT','Save Draft')));
2001 }
2002 }
2003
2004 if($this->canPublish() && !$this->IsDeletedFromStage) {
2005
2006 $actions->push(new FormAction('publish', _t('SiteTree.BUTTONSAVEPUBLISH', 'Save and Publish')));
2007 }
2008
2009
2010 $this->extend('updateCMSActions', $actions);
2011
2012 return $actions;
2013 }
2014
2015 2016 2017 2018 2019 2020
2021 function doPublish() {
2022 if (!$this->canPublish()) return false;
2023
2024 $original = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = $this->ID");
2025 if(!$original) $original = new SiteTree();
2026
2027
2028 $this->invokeWithExtensions('onBeforePublish', $original);
2029 $this->Status = "Published";
2030
2031 $this->write();
2032 $this->publish("Stage", "Live");
2033
2034 DB::query("UPDATE \"SiteTree_Live\"
2035 SET \"Sort\" = (SELECT \"SiteTree\".\"Sort\" FROM \"SiteTree\" WHERE \"SiteTree_Live\".\"ID\" = \"SiteTree\".\"ID\")
2036 WHERE EXISTS (SELECT \"SiteTree\".\"Sort\" FROM \"SiteTree\" WHERE \"SiteTree_Live\".\"ID\" = \"SiteTree\".\"ID\") AND \"ParentID\" = " . sprintf('%d', $this->ParentID) );
2037
2038
2039 $linkedPages = $this->VirtualPages();
2040 if($linkedPages) foreach($linkedPages as $page) {
2041 $page->copyFrom($page->CopyContentFrom());
2042 $page->write();
2043 if($page->ExistsOnLive) $page->doPublish();
2044 }
2045
2046
2047 $origMode = Versioned::get_reading_mode();
2048 Versioned::reading_stage('Live');
2049 foreach($this->DependentPages(false) as $page) {
2050
2051 $page->write();
2052 }
2053 Versioned::set_reading_mode($origMode);
2054
2055
2056 $usingStaticPublishing = false;
2057 foreach(ClassInfo::subclassesFor('StaticPublisher') as $class) if ($this->hasExtension($class)) $usingStaticPublishing = true;
2058
2059
2060 if (self::$write_homepage_map) {
2061 if ($usingStaticPublishing && $map = SiteTree::generate_homepage_domain_map()) {
2062 @file_put_contents(BASE_PATH.'/'.ASSETS_DIR.'/_homepage-map.php', "<?php\n\$homepageMap = ".var_export($map, true)."; ?>");
2063 } else { if (file_exists(BASE_PATH.'/'.ASSETS_DIR.'/_homepage-map.php')) unlink(BASE_PATH.'/'.ASSETS_DIR.'/_homepage-map.php'); }
2064 }
2065
2066
2067 $this->invokeWithExtensions('onAfterPublish', $original);
2068
2069 return true;
2070 }
2071
2072 static function generate_homepage_domain_map() {
2073 $domainSpecificHomepages = Versioned::get_by_stage('Page', 'Live', "\"HomepageForDomain\" != ''", "\"URLSegment\" ASC");
2074 if (!$domainSpecificHomepages) return false;
2075
2076 $map = array();
2077 foreach($domainSpecificHomepages->map('URLSegment', 'HomepageForDomain') as $url => $domains) {
2078 foreach(explode(',', $domains) as $domain) $map[$domain] = $url;
2079 }
2080 return $map;
2081 }
2082
2083 2084 2085 2086 2087 2088
2089 function doUnpublish() {
2090 if(!$this->canDeleteFromLive()) return false;
2091 if(!$this->ID) return false;
2092
2093 $this->extend('onBeforeUnpublish');
2094
2095 $origStage = Versioned::current_stage();
2096 Versioned::reading_stage('Live');
2097
2098
2099 $virtualPages = $this->VirtualPages();
2100
2101
2102 $clone = clone $this;
2103 $clone->delete();
2104
2105
2106 $dependentPages = $this->DependentPages(false);
2107 if($dependentPages) foreach($dependentPages as $page) {
2108
2109 $page->write();
2110 }
2111 Versioned::reading_stage($origStage);
2112
2113
2114 if ($virtualPages) foreach($virtualPages as $vp) $vp->doUnpublish();
2115
2116
2117
2118 if(DB::query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = $this->ID")->value()
2119 && Versioned::current_stage() != 'Live') {
2120 $this->Status = "Unpublished";
2121 $this->write();
2122 }
2123
2124 $this->extend('onAfterUnpublish');
2125
2126 return true;
2127 }
2128
2129 2130 2131 2132 2133 2134
2135 function doRollbackTo($version) {
2136 $this->publish($version, "Stage", true);
2137 $this->Status = "Saved (update)";
2138
2139 }
2140
2141 2142 2143
2144 function doRevertToLive() {
2145 $this->publish("Live", "Stage", false);
2146
2147
2148 $clone = DataObject::get_by_id("SiteTree", $this->ID);
2149 $clone->Status = "Published";
2150 $clone->writeWithoutVersion();
2151
2152
2153 foreach($this->DependentPages(false) as $page) {
2154
2155 $page->write();
2156 }
2157
2158 $this->extend('onAfterRevertToLive');
2159 }
2160
2161 2162 2163 2164
2165 function doRestoreToStage() {
2166
2167
2168 if(!DB::query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = $this->ID")->value()) {
2169 $conn = DB::getConn();
2170 if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('SiteTree', true);
2171 DB::query("INSERT INTO \"SiteTree\" (\"ID\") VALUES ($this->ID)");
2172 if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('SiteTree', false);
2173 }
2174
2175 $oldStage = Versioned::current_stage();
2176 Versioned::reading_stage('Stage');
2177 $this->forceChange();
2178 $this->writeWithoutVersion();
2179
2180 $result = DataObject::get_by_id($this->class, $this->ID);
2181
2182
2183 foreach($result->DependentPages(false) as $page) {
2184
2185 $page->write();
2186 }
2187
2188 Versioned::reading_stage($oldStage);
2189
2190 return $result;
2191 }
2192
2193 2194 2195
2196 function doDeleteFromLive() {
2197 return $this->doUnpublish();
2198 }
2199
2200 2201 2202 2203 2204 2205
2206 function isNew() {
2207 2208 2209 2210 2211 2212
2213 if(empty($this->ID)) return true;
2214
2215 if(is_numeric($this->ID)) return false;
2216
2217 return stripos($this->ID, 'new') === 0;
2218 }
2219
2220
2221 2222 2223 2224 2225
2226 function isPublished() {
2227 if($this->isNew())
2228 return false;
2229
2230 return (DB::query("SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"ID\" = $this->ID")->value())
2231 ? true
2232 : false;
2233 }
2234
2235 2236 2237 2238 2239 2240 2241
2242 protected function getClassDropdown() {
2243 $classes = self::page_type_classes();
2244 $currentClass = null;
2245 $result = array();
2246
2247 $result = array();
2248 foreach($classes as $class) {
2249 $instance = singleton($class);
2250 if((($instance instanceof HiddenClass) || !$instance->canCreate()) && ($class != $this->class)) continue;
2251
2252 $pageTypeName = $instance->i18n_singular_name();
2253
2254 if($class == $this->class) {
2255 $currentClass = $class;
2256 $result[$class] = $pageTypeName;
2257 } else {
2258 $translation = _t(
2259 'SiteTree.CHANGETO',
2260 'Change to "%s"',
2261 PR_MEDIUM,
2262 "Pagetype selection dropdown with class names"
2263 );
2264
2265
2266 if(strpos($translation, '%s') !== FALSE) {
2267 $result[$class] = sprintf(
2268 $translation,
2269 $pageTypeName
2270 );
2271 } else {
2272 $result[$class] = "{$translation} \"{$pageTypeName}\"";
2273 }
2274 }
2275
2276
2277
2278
2279
2280 if(i18n::get_locale() != 'en_US') {
2281 $result[$class] = $result[$class] . " ({$class})";
2282 }
2283 }
2284
2285
2286 asort($result);
2287 if($currentClass) {
2288 $currentPageTypeName = $result[$currentClass];
2289 unset($result[$currentClass]);
2290 $result = array_reverse($result);
2291 $result[$currentClass] = $currentPageTypeName;
2292 $result = array_reverse($result);
2293 }
2294
2295 return $result;
2296 }
2297
2298
2299 2300 2301 2302 2303 2304
2305 function allowedChildren() {
2306 $allowedChildren = array();
2307 $candidates = $this->stat('allowed_children');
2308 if($candidates && $candidates != "none" && $candidates != "SiteTree_root") {
2309 foreach($candidates as $candidate) {
2310 if(substr($candidate,0,1) == '*') {
2311 $allowedChildren[] = substr($candidate,1);
2312 } else {
2313 $subclasses = ClassInfo::subclassesFor($candidate);
2314 foreach($subclasses as $subclass) {
2315 if (ClassInfo::exists($subclass) && $subclass != "SiteTree_root") {
2316 $defParent = singleton($subclass)->defaultParent();
2317 if (!$defParent || ($defParent == $this->class)) {
2318 $allowedChildren[] = $subclass;
2319 }
2320 }
2321 }
2322 }
2323 }
2324 return $allowedChildren;
2325 }
2326 }
2327
2328
2329 2330 2331 2332 2333
2334 function defaultChild() {
2335 $default = $this->stat('default_child');
2336 $allowed = $this->allowedChildren();
2337 if($allowed) {
2338 if(!$default || !in_array($default, $allowed))
2339 $default = reset($allowed);
2340 return $default;
2341 }
2342 }
2343
2344
2345 2346 2347 2348 2349 2350
2351 function defaultParent() {
2352 return $this->stat('default_parent');
2353 }
2354
2355
2356 2357 2358 2359 2360
2361 function cmsCleanup_parentChanged() {
2362 }
2363
2364
2365 2366 2367 2368 2369 2370
2371 function (){
2372 if($value = $this->getField("MenuTitle")) {
2373 return $value;
2374 } else {
2375 return $this->getField("Title");
2376 }
2377 }
2378
2379
2380 2381 2382 2383 2384
2385 function ($value) {
2386 if($value == $this->getField("Title")) {
2387 $this->setField("MenuTitle", null);
2388 } else {
2389 $this->setField("MenuTitle", $value);
2390 }
2391 }
2392
2393 2394 2395 2396 2397 2398
2399 function TreeTitle() {
2400 if($this->IsDeletedFromStage) {
2401 if($this->ExistsOnLive) {
2402 $tag ="del title=\"" . _t('SiteTree.REMOVEDFROMDRAFT', 'Removed from draft site') . "\"";
2403 } else {
2404 $tag ="del class=\"deletedOnLive\" title=\"" . _t('SiteTree.DELETEDPAGE', 'Deleted page') . "\"";
2405 }
2406 } elseif($this->IsAddedToStage) {
2407 $tag = "ins title=\"" . _t('SiteTree.ADDEDTODRAFT', 'Added to draft site') . "\"";
2408 } elseif($this->IsModifiedOnStage) {
2409 $tag = "span title=\"" . _t('SiteTree.MODIFIEDONDRAFT', 'Modified on draft site') . "\" class=\"modified\"";
2410 } else {
2411 $tag = '';
2412 }
2413
2414 $text = Convert::raw2xml(str_replace(array("\n","\r"),"",$this->MenuTitle));
2415 return ($tag) ? "<$tag>" . $text . "</" . strtok($tag,' ') . ">" : $text;
2416 }
2417
2418 2419 2420 2421
2422 public function Level($level) {
2423 $parent = $this;
2424 $stack = array($parent);
2425 while($parent = $parent->Parent) {
2426 array_unshift($stack, $parent);
2427 }
2428
2429 return isset($stack[$level-1]) ? $stack[$level-1] : null;
2430 }
2431
2432 2433 2434 2435 2436 2437 2438
2439 function CMSTreeClasses($controller) {
2440 $classes = $this->class;
2441 if($this->HasBrokenFile || $this->HasBrokenLink)
2442 $classes .= " BrokenLink";
2443
2444 if(!$this->canAddChildren())
2445 $classes .= " nochildren";
2446
2447 if($controller->isCurrentPage($this))
2448 $classes .= " current";
2449
2450 if(!$this->canEdit() && !$this->canAddChildren())
2451 $classes .= " disabled";
2452
2453 if(!$this->ShowInMenus)
2454 $classes .= " notinmenu";
2455
2456
2457 2458 2459 2460
2461 $classes .= $this->markingClasses();
2462
2463 return $classes;
2464 }
2465
2466 2467 2468 2469 2470 2471 2472
2473 function getIsDeletedFromStage() {
2474 if(!$this->ID) return true;
2475 if($this->isNew()) return false;
2476
2477 $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
2478
2479
2480 return !($stageVersion);
2481 }
2482
2483 2484 2485
2486 function getExistsOnLive() {
2487 return (bool)Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
2488 }
2489
2490 2491 2492 2493 2494 2495 2496
2497 public function getIsModifiedOnStage() {
2498
2499 if($this->isNew()) return false;
2500
2501 $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
2502 $liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
2503
2504 return ($stageVersion != $liveVersion);
2505 }
2506
2507 2508 2509 2510 2511 2512 2513
2514 public function getIsAddedToStage() {
2515
2516 if($this->isNew()) return false;
2517
2518 $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
2519 $liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
2520
2521 return ($stageVersion && !$liveVersion);
2522 }
2523
2524 2525 2526 2527 2528 2529
2530 public static function disableCMSFieldsExtensions() {
2531 self::$runCMSFieldsExtensions = false;
2532 }
2533
2534 2535 2536 2537
2538 public static function enableCMSFieldsExtensions() {
2539 self::$runCMSFieldsExtensions = true;
2540 }
2541
2542 function providePermissions() {
2543 return array(
2544 'SITETREE_GRANT_ACCESS' => array(
2545 'name' => _t('SiteTree.PERMISSION_GRANTACCESS_DESCRIPTION', 'Manage access rights for content'),
2546 'help' => _t('SiteTree.PERMISSION_GRANTACCESS_HELP', 'Allow setting of page-specific access restrictions in the "Pages" section.'),
2547 'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
2548 'sort' => 100
2549 ),
2550 'SITETREE_VIEW_ALL' => array(
2551 'name' => _t('SiteTree.VIEW_ALL_DESCRIPTION', 'View any page'),
2552 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2553 'sort' => -100,
2554 'help' => _t('SiteTree.VIEW_ALL_HELP', 'Ability to view any page on the site, regardless of the settings on the Access tab. Requires the "Access to Site Content" permission')
2555 ),
2556 'SITETREE_EDIT_ALL' => array(
2557 'name' => _t('SiteTree.EDIT_ALL_DESCRIPTION', 'Edit any page'),
2558 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2559 'sort' => -50,
2560 'help' => _t('SiteTree.EDIT_ALL_HELP', 'Ability to edit any page on the site, regardless of the settings on the Access tab. Requires the "Access to Site Content" permission')
2561 ),
2562 'SITETREE_REORGANISE' => array(
2563 'name' => _t('SiteTree.REORGANISE_DESCRIPTION', 'Change site structure'),
2564 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2565 'help' => _t('SiteTree.REORGANISE_HELP', 'Rearrange pages in the site tree through drag&drop.'),
2566 'sort' => 100
2567 ),
2568 'VIEW_DRAFT_CONTENT' => array(
2569 'name' => _t('SiteTree.VIEW_DRAFT_CONTENT', 'View draft content'),
2570 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2571 'help' => _t('SiteTree.VIEW_DRAFT_CONTENT_HELP', 'Applies to viewing pages outside of the CMS in draft mode. Useful for external collaborators without CMS access.'),
2572 'sort' => 100
2573 )
2574 );
2575 }
2576
2577 2578 2579 2580 2581
2582 function i18n_singular_name() {
2583 return _t($this->class.'.SINGULARNAME', $this->singular_name());
2584 }
2585
2586 2587 2588 2589
2590 function provideI18nEntities() {
2591 $entities = parent::provideI18nEntities();
2592
2593 if(isset($entities['Page.SINGULARNAME'])) $entities['Page.SINGULARNAME'][3] = 'sapphire';
2594 if(isset($entities['Page.PLURALNAME'])) $entities['Page.PLURALNAME'][3] = 'sapphire';
2595
2596 return $entities;
2597 }
2598
2599 function getParentType() {
2600 return $this->ParentID == 0 ? 'root' : 'subpage';
2601 }
2602
2603 static function reset() {
2604 self::$cache_permissions = array();
2605 }
2606
2607 }
2608
2609 ?>
2610
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-