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
1355
1356 if(!$segment) {
1357 $segment = "page-$this->ID";
1358 }
1359 $this->URLSegment = $segment;
1360 }
1361
1362 DataObject::set_context_obj($this);
1363
1364
1365 $count = 2;
1366 while(!$this->validURLSegment()) {
1367 $this->URLSegment = preg_replace('/-[0-9]+$/', null, $this->URLSegment) . '-' . $count;
1368 $count++;
1369 }
1370
1371 DataObject::set_context_obj(null);
1372
1373 $this->syncLinkTracking();
1374
1375
1376 $fieldsIgnoredByVersioning = array('HasBrokenLink', 'Status', 'HasBrokenFile', 'ToDo');
1377 $changedFields = array_keys($this->getChangedFields(true, 2));
1378
1379
1380
1381
1382
1383 $oneChangedFields = array_keys($this->getChangedFields(true, 1));
1384
1385 if($oneChangedFields && !array_diff($changedFields, $fieldsIgnoredByVersioning)) {
1386
1387 $this->migrateVersion($this->Version);
1388 }
1389 }
1390
1391 function syncLinkTracking() {
1392
1393 $allFields = $this->db();
1394 $htmlFields = array();
1395 foreach($allFields as $field => $fieldSpec) {
1396 if ($field == 'ExtraMeta') continue;
1397 if(preg_match('/([^(]+)/', $fieldSpec, $matches)) {
1398 $class = $matches[0];
1399 if(class_exists($class)){
1400 if($class == 'HTMLText' || is_subclass_of($class, 'HTMLText')) $htmlFields[] = $field;
1401 }
1402 }
1403 }
1404
1405 $linkedPages = array();
1406 $linkedFiles = array();
1407 $this->HasBrokenLink = false;
1408 $this->HasBrokenFile = false;
1409
1410 foreach($htmlFields as $field) {
1411 $formField = new HTMLEditorField($field);
1412 $formField->setValue($this->$field);
1413 $formField->saveInto($this);
1414 }
1415
1416
1417 if ($this->ID) {
1418 if (($has_one = $this->has_one()) && count($has_one)) {
1419 foreach($has_one as $name => $type) {
1420 if (singleton($type)->is_a('File') && !singleton($type)->is_a('Folder') && $this->{"{$name}ID"}) {
1421 if (!DataObject::get_by_id($type, $this->{"{$name}ID"})) {
1422 $this->HasBrokenFile = true;
1423 }
1424 }
1425 }
1426 }
1427 }
1428 $this->extend('augmentSyncLinkTracking');
1429 }
1430
1431 function onAfterWrite() {
1432
1433 $this->flushCache();
1434
1435
1436 $linkedPages = $this->VirtualPages();
1437 if($linkedPages) foreach($linkedPages as $page) {
1438 $page->copyFrom($page->CopyContentFrom());
1439 $page->write();
1440 }
1441
1442 parent::onAfterWrite();
1443 }
1444
1445 function onBeforeDelete() {
1446 parent::onBeforeDelete();
1447
1448
1449 if(SiteTree::get_enforce_strict_hierarchy() && $children = $this->AllChildren(2)) {
1450 foreach($children as $child) {
1451 $child->delete();
1452 }
1453 }
1454 }
1455
1456
1457 function onAfterDelete() {
1458
1459 $this->flushCache();
1460
1461
1462 $dependentPages = $this->DependentPages();
1463 if($dependentPages) foreach($dependentPages as $page) {
1464
1465 $page->write();
1466 }
1467
1468 parent::onAfterDelete();
1469 }
1470
1471 1472 1473 1474 1475 1476 1477 1478 1479
1480 public function validURLSegment() {
1481 if(self::nested_urls() && $parent = $this->Parent()) {
1482 if($controller = ModelAsController::controller_for($parent)) {
1483 if($controller instanceof Controller && $controller->hasAction($this->URLSegment)) return false;
1484 }
1485 }
1486
1487 if(!self::nested_urls() || !$this->ParentID) {
1488 if(class_exists($this->URLSegment) && is_subclass_of($this->URLSegment, 'RequestHandler')) return false;
1489 }
1490
1491 $IDFilter = ($this->ID) ? "AND \"SiteTree\".\"ID\" <> $this->ID" : null;
1492 $parentFilter = null;
1493
1494 if(self::nested_urls()) {
1495 if($this->ParentID) {
1496 $parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID";
1497 } else {
1498 $parentFilter = ' AND "SiteTree"."ParentID" = 0';
1499 }
1500 }
1501
1502 $existingPage = DataObject::get_one(
1503 'SiteTree',
1504 "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter"
1505 );
1506 if ($existingPage) {
1507 return false;
1508 }
1509
1510 $values = $this->extend('augmentValidURLSegment');
1511 if (count($values) && !min($values)) {
1512 return false;
1513 }
1514
1515 return true;
1516 }
1517
1518 1519 1520 1521 1522
1523 function generateURLSegment($title){
1524 $t = strtolower($title);
1525 $t = Convert::rus2lat($t);
1526 $t = str_replace('&','-and-',$t);
1527 $t = str_replace('&','-and-',$t);
1528 $t = ereg_replace('[^A-Za-z0-9]+','-',$t);
1529 $t = ereg_replace('-+','-',$t);
1530 if(!$t || $t == '-' || $t == '-1') {
1531 $t = "page-$this->ID";
1532 }
1533 return trim($t, '-');
1534 }
1535
1536 1537 1538 1539
1540 function rewriteFileURL($old, $new) {
1541 $fields = $this->inheritedDatabaseFields();
1542
1543 foreach(array("SiteTree_Live", "SiteTree") as $table) {
1544
1545 $published = DB::query("SELECT * FROM \"$table\" WHERE \"ID\" = $this->ID")->record();
1546 $origPublished = $published;
1547
1548 foreach($fields as $fieldName => $fieldType) {
1549 if ($fieldType != 'HTMLText') continue;
1550
1551
1552 if(isset($published[$fieldName])) {
1553
1554 $oldFileMask = '!' . dirname($old) . '/(_resampled/resizedimage[0-9]+-)?' . basename($old) . '!';
1555 $published[$fieldName] = preg_replace($oldFileMask, $new, $published[$fieldName], -1, $numReplaced);
1556 if($numReplaced) {
1557 DB::query("UPDATE \"$table\" SET \"$fieldName\" = '"
1558 . Convert::raw2sql($published[$fieldName]) . "' WHERE \"ID\" = $this->ID");
1559
1560
1561 if($table == 'SiteTree_Live') {
1562 $publishedClass = $origPublished['ClassName'];
1563 $origPublishedObj = new $publishedClass($origPublished);
1564 $this->extend('onRenameLinkedAsset', $origPublishedObj);
1565 }
1566 }
1567 }
1568 }
1569 }
1570 }
1571
1572 1573 1574 1575 1576 1577
1578 function DependentPages($includeVirtuals = true) {
1579 if(is_callable('Subsite::disable_subsite_filter')) Subsite::disable_subsite_filter(true);
1580
1581
1582 $items = $this->BackLinkTracking();
1583 if(!$items) $items = new DataObjectSet();
1584 else foreach($items as $item) $item->DependentLinkType = 'Content link';
1585
1586
1587 if($includeVirtuals) {
1588 $virtuals = $this->VirtualPages();
1589 if($virtuals) {
1590 foreach($virtuals as $item) $item->DependentLinkType = 'Virtual page';
1591 $items->merge($virtuals);
1592 }
1593 }
1594
1595
1596 $redirectors = DataObject::get("RedirectorPage", "\"RedirectionType\" = 'Internal' AND \"LinkToID\" = $this->ID");
1597 if($redirectors) {
1598 foreach($redirectors as $item) $item->DependentLinkType = 'Redirector page';
1599 $items->merge($redirectors);
1600 }
1601
1602 if(is_callable('Subsite::disable_subsite_filter')) Subsite::disable_subsite_filter(false);
1603 return $items;
1604 }
1605
1606 1607 1608 1609 1610
1611 function DependentPagesCount($includeVirtuals = true) {
1612 $links = DB::query("SELECT COUNT(*) FROM \"SiteTree_LinkTracking\"
1613 INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"SiteTree_LinkTracking\".\"SiteTreeID\"
1614 WHERE \"ChildID\" = $this->ID ")->value();
1615 if($includeVirtuals) {
1616 $virtuals = DB::query("SELECT COUNT(*) FROM \"VirtualPage\"
1617 INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"VirtualPage\".\"ID\"
1618 WHERE \"CopyContentFromID\" = $this->ID")->value();
1619 } else {
1620 $virtuals = 0;
1621 }
1622 $redirectors = DB::query("SELECT COUNT(*) FROM \"RedirectorPage\"
1623 INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"RedirectorPage\".\"ID\"
1624 WHERE \"RedirectionType\" = 'Internal' AND \"LinkToID\" = $this->ID")->value();
1625
1626
1627 return 0 + $links + $virtuals + $redirectors;
1628 }
1629
1630 1631 1632
1633 function VirtualPages() {
1634 if(!$this->ID) return null;
1635 if(class_exists('Subsite')) {
1636 return Subsite::get_from_all_subsites('VirtualPage', "\"CopyContentFromID\" = " . (int)$this->ID);
1637 } else {
1638 return DataObject::get('VirtualPage', "\"CopyContentFromID\" = " . (int)$this->ID);
1639 }
1640 }
1641
1642 1643 1644 1645 1646 1647 1648 1649 1650
1651 function getCMSFields() {
1652 require_once("forms/Form.php");
1653 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
1654 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
1655 Requirements::javascript(CMS_DIR . "/javascript/SitetreeAccess.js");
1656 Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
1657 Requirements::javascript(SAPPHIRE_DIR . '/javascript/UpdateURL.js');
1658
1659
1660
1661 if($this->ID && is_numeric($this->ID)) {
1662 $linkedPages = $this->VirtualPages();
1663 }
1664
1665 $parentPageLinks = array();
1666
1667 if(isset($linkedPages)) {
1668 foreach($linkedPages as $linkedPage) {
1669 $parentPage = $linkedPage->Parent;
1670 if($parentPage) {
1671 if($parentPage->ID) {
1672 $parentPageLinks[] = "<a class=\"cmsEditlink\" href=\"admin/show/$linkedPage->ID\">{$parentPage->Title}</a>";
1673 } else {
1674 $parentPageLinks[] = "<a class=\"cmsEditlink\" href=\"admin/show/$linkedPage->ID\">" .
1675 _t('SiteTree.TOPLEVEL', 'Site Content (Top Level)') .
1676 "</a>";
1677 }
1678 }
1679 }
1680
1681 $lastParent = array_pop($parentPageLinks);
1682 $parentList = "'$lastParent'";
1683
1684 if(count($parentPageLinks) > 0) {
1685 $parentList = "'" . implode("', '", $parentPageLinks) . "' and "
1686 . $parentList;
1687 }
1688
1689 $statusMessage[] = sprintf(
1690 _t('SiteTree.APPEARSVIRTUALPAGES', "This content also appears on the virtual pages in the %s sections."),
1691 $parentList
1692 );
1693 }
1694
1695 if($this->HasBrokenLink || $this->HasBrokenFile) {
1696 $statusMessage[] = _t('SiteTree.HASBROKENLINKS', "This page has broken links.");
1697 }
1698
1699 $message = "STATUS: $this->Status<br />";
1700 if(isset($statusMessage)) {
1701 $message .= "NOTE: " . implode("<br />", $statusMessage);
1702 }
1703
1704 $dependentNote = '';
1705 $dependentTable = new LiteralField('DependentNote', '<p>'._t('SiteTree.DEPENDENTNOTE','No dependent pages').'</p>');
1706
1707
1708 $dependentPagesCount = $this->DependentPagesCount();
1709 if($dependentPagesCount) {
1710 $dependentColumns = array(
1711 'Title' => $this->fieldLabel('Title'),
1712 'AbsoluteLink' => _t('SiteTree.DependtPageColumnURL', 'URL'),
1713 'DependentLinkType' => _t('SiteTree.DependtPageColumnLinkType', 'Link type'),
1714 );
1715 if(class_exists('Subsite')) $dependentColumns['Subsite.Title'] = singleton('Subsite')->i18n_singular_name();
1716
1717 $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>');
1718 $dependentTable = new TableListField(
1719 'DependentPages',
1720 'SiteTree',
1721 $dependentColumns
1722 );
1723 $dependentTable->setCustomSourceItems($this->DependentPages());
1724 $dependentTable->setFieldFormatting(array(
1725 'Title' => '<a href=\"admin/show/$ID\">$Title</a>',
1726 'AbsoluteLink' => '<a href=\"$value\">$value</a>',
1727 ));
1728 $dependentTable->setPermissions(array(
1729 'show',
1730 'export'
1731 ));
1732 }
1733
1734
1735 $fields = new FieldSet(
1736 $rootTab = new TabSet("Root",
1737 $tabContent = new TabSet('Content',
1738 $tabMain = new Tab('Main',
1739 new TextField("Title", $this->fieldLabel('Title')),
1740 new TextField("MenuTitle", $this->fieldLabel('MenuTitle')),
1741 new HtmlEditorField("Content", _t('SiteTree.HTMLEDITORTITLE', "Content", PR_MEDIUM, 'HTML editor title'))
1742 ),
1743 $tabMeta = new Tab('Metadata',
1744 new FieldGroup(_t('SiteTree.URL', "URL"),
1745 new LabelField('BaseUrlLabel',Controller::join_links (
1746 Director::absoluteBaseURL(),
1747 (self::nested_urls() && $this->ParentID ? $this->Parent()->RelativeLink(true) : null)
1748 )),
1749 new UniqueRestrictedTextField("URLSegment",
1750 "URLSegment",
1751 "SiteTree",
1752 _t('SiteTree.VALIDATIONURLSEGMENT1', "Another page is using that URL. URL must be unique for each page"),
1753 "[^A-Za-z0-9_-]+",
1754 "-",
1755 _t('SiteTree.VALIDATIONURLSEGMENT2', "URLs can only be made up of letters, digits and hyphens."),
1756 "",
1757 "",
1758 250
1759 ),
1760 new LabelField('TrailingSlashLabel',"/")
1761 ),
1762 new LiteralField('LinkChangeNote', self::nested_urls() && count($this->AllChildren()) ?
1763 '<p>' . $this->fieldLabel('LinkChangeNote'). '</p>' : null
1764 ),
1765 new HeaderField('MetaTagsHeader',$this->fieldLabel('MetaTagsHeader')),
1766 new TextField("MetaTitle", $this->fieldLabel('MetaTitle')),
1767 new TextareaField("MetaKeywords", $this->fieldLabel('MetaKeywords'), 1),
1768 new TextareaField("MetaDescription", $this->fieldLabel('MetaDescription')),
1769 new TextareaField("ExtraMeta",$this->fieldLabel('ExtraMeta'))
1770 )
1771 ),
1772 $tabBehaviour = new Tab('Behaviour',
1773 new DropdownField(
1774 "ClassName",
1775 $this->fieldLabel('ClassName'),
1776 $this->getClassDropdown()
1777 ),
1778
1779 new OptionsetField("ParentType", _t("SiteTree.PAGELOCATION", "Page location"), array(
1780 "root" => _t("SiteTree.PARENTTYPE_ROOT", "Top-level page"),
1781 "subpage" => _t("SiteTree.PARENTTYPE_SUBPAGE", "Sub-page underneath a parent page (choose below)"),
1782 )),
1783 $parentIDField = new TreeDropdownField("ParentID", $this->fieldLabel('ParentID'), 'SiteTree'),
1784
1785 new CheckboxField("ShowInMenus", $this->fieldLabel('ShowInMenus')),
1786 new CheckboxField("ShowInSearch", $this->fieldLabel('ShowInSearch')),
1787
1788 new CheckboxField("ProvideComments", $this->fieldLabel('ProvideComments')),
1789 new LiteralField(
1790 "HomepageForDomainInfo",
1791 "<p>" .
1792 _t('SiteTree.NOTEUSEASHOMEPAGE',
1793 "Use this page as the 'home page' for the following domains:
1794 (separate multiple domains with commas)") .
1795 "</p>"
1796 ),
1797 new TextField(
1798 "HomepageForDomain",
1799 _t('SiteTree.HOMEPAGEFORDOMAIN', "Domain(s)", PR_MEDIUM, 'Listing domains that should be used as homepage')
1800 ),
1801 new NumericField(
1802 "NumberCMSChildren",
1803 _t('SiteTree.NUMBERCMSCHILDREN',"Total displayed subpages in CMS (must be numeric, defaults to 0 == all, page refresh required after changing this)"),
1804 $this->NumberCMSChildren
1805 )
1806
1807 ),
1808 $tabToDo = new Tab('Todo',
1809 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>")),
1810 new TextareaField("ToDo", "")
1811 ),
1812 $tabDependent = new Tab('Dependent',
1813 $dependentNote,
1814 $dependentTable
1815 ),
1816 $tabAccess = new Tab('Access',
1817 new HeaderField('WhoCanViewHeader',_t('SiteTree.ACCESSHEADER', "Who can view this page?"), 2),
1818 $viewersOptionsField = new OptionsetField(
1819 "CanViewType",
1820 ""
1821 ),
1822 $viewerGroupsField = new TreeMultiselectField("ViewerGroups", $this->fieldLabel('ViewerGroups')),
1823 new HeaderField('WhoCanEditHeader',_t('SiteTree.EDITHEADER', "Who can edit this page?"), 2),
1824 $editorsOptionsField = new OptionsetField(
1825 "CanEditType",
1826 ""
1827 ),
1828 $editorGroupsField = new TreeMultiselectField("EditorGroups", $this->fieldLabel('EditorGroups'))
1829 )
1830 )
1831
1832 );
1833
1834 if ($this->stat('allowed_children') == 'none')
1835 $fields->removeByName("NumberCMSChildren");
1836
1837
1838
1839 1840 1841 1842
1843 $parentIDField->setFilterFunction(create_function('$node', "return \$node->ID != {$this->ID};"));
1844
1845
1846 if($dependentPagesCount) $tabDependent->setTitle(_t('SiteTree.TABDEPENDENT', "Dependent pages") . " ($dependentPagesCount)");
1847 else $fields->removeFieldFromTab('Root', 'Dependent');
1848
1849
1850 if(!Permission::check("SITETREE_REORGANISE")) {
1851 $fields->makeFieldReadonly('ParentType');
1852 if($this->ParentType == 'root') {
1853 $fields->removeByName('ParentID');
1854 } else {
1855 $fields->makeFieldReadonly('ParentID');
1856 }
1857 }
1858
1859 $viewersOptionsSource = array();
1860 $viewersOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page");
1861 $viewersOptionsSource["Anyone"] = _t('SiteTree.ACCESSANYONE', "Anyone");
1862 $viewersOptionsSource["LoggedInUsers"] = _t('SiteTree.ACCESSLOGGEDIN', "Logged-in users");
1863 $viewersOptionsSource["OnlyTheseUsers"] = _t('SiteTree.ACCESSONLYTHESE', "Only these people (choose from list)");
1864 $viewersOptionsField->setSource($viewersOptionsSource);
1865
1866 $editorsOptionsSource = array();
1867 $editorsOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page");
1868 $editorsOptionsSource["LoggedInUsers"] = _t('SiteTree.EDITANYONE', "Anyone who can log-in to the CMS");
1869 $editorsOptionsSource["OnlyTheseUsers"] = _t('SiteTree.EDITONLYTHESE', "Only these people (choose from list)");
1870 $editorsOptionsField->setSource($editorsOptionsSource);
1871
1872 if(!Permission::check('SITETREE_GRANT_ACCESS')) {
1873 $fields->makeFieldReadonly($viewersOptionsField);
1874 if($this->CanViewType == 'OnlyTheseUsers') {
1875 $fields->makeFieldReadonly($viewerGroupsField);
1876 } else {
1877 $fields->removeByName('ViewerGroups');
1878 }
1879
1880 $fields->makeFieldReadonly($editorsOptionsField);
1881 if($this->CanEditType == 'OnlyTheseUsers') {
1882 $fields->makeFieldReadonly($editorGroupsField);
1883 } else {
1884 $fields->removeByName('EditorGroups');
1885 }
1886 }
1887
1888 $tabContent->setTitle(_t('SiteTree.TABCONTENT', "Content"));
1889 $tabMain->setTitle(_t('SiteTree.TABMAIN', "Main"));
1890 $tabMeta->setTitle(_t('SiteTree.TABMETA', "Metadata"));
1891 $tabBehaviour->setTitle(_t('SiteTree.TABBEHAVIOUR', "Behaviour"));
1892 $tabAccess->setTitle(_t('SiteTree.TABACCESS', "Access"));
1893 $tabToDo->setTitle(_t('SiteTree.TODO', "To Do") . ($this->ToDo ? ' **' : ''));
1894
1895 if(self::$runCMSFieldsExtensions) {
1896 $this->extend('updateCMSFields', $fields);
1897 }
1898
1899 return $fields;
1900 }
1901
1902 1903 1904 1905 1906
1907 function fieldLabels($includerelations = true) {
1908 $labels = parent::fieldLabels($includerelations);
1909
1910 $labels['Title'] = _t('SiteTree.PAGETITLE', "Page name");
1911 $labels['MenuTitle'] = _t('SiteTree.MENUTITLE', "Navigation label");
1912 $labels['MetaTagsHeader'] = _t('SiteTree.METAHEADER', "Search Engine Meta-tags");
1913 $labels['MetaTitle'] = _t('SiteTree.METATITLE', "Title");
1914 $labels['MetaDescription'] = _t('SiteTree.METADESC', "Description");
1915 $labels['MetaKeywords'] = _t('SiteTree.METAKEYWORDS', "Keywords");
1916 $labels['ExtraMeta'] = _t('SiteTree.METAEXTRA', "Custom Meta Tags");
1917 $labels['ClassName'] = _t('SiteTree.PAGETYPE', "Page type", PR_MEDIUM, 'Classname of a page object');
1918 $labels['ParentType'] = _t('SiteTree.PARENTTYPE', "Page location", PR_MEDIUM);
1919 $labels['ParentID'] = _t('SiteTree.PARENTID', "Parent page", PR_MEDIUM);
1920 $labels['ShowInMenus'] =_t('SiteTree.SHOWINMENUS', "Show in menus?");
1921 $labels['ShowInSearch'] = _t('SiteTree.SHOWINSEARCH', "Show in search?");
1922 $labels['ProvideComments'] = _t('SiteTree.ALLOWCOMMENTS', "Allow comments on this page?");
1923 $labels['ViewerGroups'] = _t('SiteTree.VIEWERGROUPS', "Viewer Groups");
1924 $labels['EditorGroups'] = _t('SiteTree.EDITORGROUPS', "Editor Groups");
1925 $labels['URLSegment'] = _t('SiteTree.URLSegment', 'URL Segment', PR_MEDIUM, 'URL for this page');
1926 $labels['Content'] = _t('SiteTree.Content', 'Content', PR_MEDIUM, 'Main HTML Content for a page');
1927 $labels['HomepageForDomain'] = _t('SiteTree.HomepageForDomain', 'Hompage for this domain');
1928 $labels['CanViewType'] = _t('SiteTree.Viewers', 'Viewers Groups');
1929 $labels['CanEditType'] = _t('SiteTree.Editors', 'Editors Groups');
1930 $labels['ToDo'] = _t('SiteTree.ToDo', 'Todo Notes');
1931 $labels['Comments'] = _t('SiteTree.Comments', 'Comments');
1932 $labels['LinkChangeNote'] = _t (
1933 'SiteTree.LINKCHANGENOTE', 'Changing this page\'s link will also affect the links of all child pages.'
1934 );
1935
1936 if($includerelations){
1937 $labels['Parent'] = _t('SiteTree.has_one_Parent', 'Parent Page', PR_MEDIUM, 'The parent page in the site hierarchy');
1938 $labels['LinkTracking'] = _t('SiteTree.many_many_LinkTracking', 'Link Tracking');
1939 $labels['BackLinkTracking'] = _t('SiteTree.many_many_BackLinkTracking', 'Backlink Tracking');
1940 }
1941
1942 return $labels;
1943 }
1944
1945
1946
1947 1948 1949 1950
1951 function getCMSActions() {
1952 $actions = new FieldSet();
1953
1954 if($this->isPublished() && $this->canPublish() && !$this->IsDeletedFromStage) {
1955
1956 $unpublish = FormAction::create('unpublish', _t('SiteTree.BUTTONUNPUBLISH', 'Unpublish'), 'delete');
1957 $unpublish->describe(_t('SiteTree.BUTTONUNPUBLISHDESC', "Remove this page from the published site"));
1958 $unpublish->addExtraClass('delete');
1959 $actions->push($unpublish);
1960 }
1961
1962 if($this->stagesDiffer('Stage', 'Live') && !$this->IsDeletedFromStage) {
1963 if($this->isPublished() && $this->canEdit()) {
1964
1965 $rollback = FormAction::create('rollback', _t('SiteTree.BUTTONCANCELDRAFT', 'Cancel draft changes'), 'delete');
1966 $rollback->describe(_t('SiteTree.BUTTONCANCELDRAFTDESC', "Delete your draft and revert to the currently published page"));
1967 $rollback->addExtraClass('delete');
1968 $actions->push($rollback);
1969 }
1970 }
1971
1972 if($this->canEdit()) {
1973 if($this->IsDeletedFromStage) {
1974 if($this->ExistsOnLive) {
1975
1976 $actions->push(new FormAction('revert',_t('CMSMain.RESTORE','Restore')));
1977 if($this->canDelete() && $this->canDeleteFromLive()) {
1978
1979 $actions->push(new FormAction('deletefromlive',_t('CMSMain.DELETEFP','Unpublish')));
1980 }
1981 } else {
1982
1983 $actions->push(new FormAction('restore',_t('CMSMain.RESTORE','Restore')));
1984 }
1985 } else {
1986 if($this->canDelete()) {
1987
1988 $actions->push($deleteAction = new FormAction('delete',_t('CMSMain.DELETE_DRAFT','Delete Draft')));
1989 $deleteAction->addExtraClass('delete');
1990 }
1991
1992
1993 $actions->push(new FormAction('save',_t('CMSMain.SAVE_DRAFT','Save Draft')));
1994 }
1995 }
1996
1997 if($this->canPublish() && !$this->IsDeletedFromStage) {
1998
1999 $actions->push(new FormAction('publish', _t('SiteTree.BUTTONSAVEPUBLISH', 'Save and Publish')));
2000 }
2001
2002
2003 $this->extend('updateCMSActions', $actions);
2004
2005 return $actions;
2006 }
2007
2008 2009 2010 2011 2012 2013
2014 function doPublish() {
2015 if (!$this->canPublish()) return false;
2016
2017 $original = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = $this->ID");
2018 if(!$original) $original = new SiteTree();
2019
2020
2021 $this->invokeWithExtensions('onBeforePublish', $original);
2022 $this->Status = "Published";
2023
2024 $this->write();
2025 $this->publish("Stage", "Live");
2026
2027 DB::query("UPDATE \"SiteTree_Live\"
2028 SET \"Sort\" = (SELECT \"SiteTree\".\"Sort\" FROM \"SiteTree\" WHERE \"SiteTree_Live\".\"ID\" = \"SiteTree\".\"ID\")
2029 WHERE EXISTS (SELECT \"SiteTree\".\"Sort\" FROM \"SiteTree\" WHERE \"SiteTree_Live\".\"ID\" = \"SiteTree\".\"ID\") AND \"ParentID\" = " . sprintf('%d', $this->ParentID) );
2030
2031
2032 $linkedPages = $this->VirtualPages();
2033 if($linkedPages) foreach($linkedPages as $page) {
2034 $page->copyFrom($page->CopyContentFrom());
2035 $page->write();
2036 if($page->ExistsOnLive) $page->doPublish();
2037 }
2038
2039
2040 $origMode = Versioned::get_reading_mode();
2041 Versioned::reading_stage('Live');
2042 foreach($this->DependentPages(false) as $page) {
2043
2044 $page->write();
2045 }
2046 Versioned::set_reading_mode($origMode);
2047
2048
2049 $usingStaticPublishing = false;
2050 foreach(ClassInfo::subclassesFor('StaticPublisher') as $class) if ($this->hasExtension($class)) $usingStaticPublishing = true;
2051
2052
2053 if (self::$write_homepage_map) {
2054 if ($usingStaticPublishing && $map = SiteTree::generate_homepage_domain_map()) {
2055 @file_put_contents(BASE_PATH.'/'.ASSETS_DIR.'/_homepage-map.php', "<?php\n\$homepageMap = ".var_export($map, true)."; ?>");
2056 } else { if (file_exists(BASE_PATH.'/'.ASSETS_DIR.'/_homepage-map.php')) unlink(BASE_PATH.'/'.ASSETS_DIR.'/_homepage-map.php'); }
2057 }
2058
2059
2060 $this->invokeWithExtensions('onAfterPublish', $original);
2061
2062 return true;
2063 }
2064
2065 static function generate_homepage_domain_map() {
2066 $domainSpecificHomepages = Versioned::get_by_stage('Page', 'Live', "\"HomepageForDomain\" != ''", "\"URLSegment\" ASC");
2067 if (!$domainSpecificHomepages) return false;
2068
2069 $map = array();
2070 foreach($domainSpecificHomepages->map('URLSegment', 'HomepageForDomain') as $url => $domains) {
2071 foreach(explode(',', $domains) as $domain) $map[$domain] = $url;
2072 }
2073 return $map;
2074 }
2075
2076 2077 2078 2079 2080 2081
2082 function doUnpublish() {
2083 if(!$this->canDeleteFromLive()) return false;
2084 if(!$this->ID) return false;
2085
2086 $this->extend('onBeforeUnpublish');
2087
2088 $origStage = Versioned::current_stage();
2089 Versioned::reading_stage('Live');
2090
2091
2092 $virtualPages = $this->VirtualPages();
2093
2094
2095 $clone = clone $this;
2096 $clone->delete();
2097
2098
2099 $dependentPages = $this->DependentPages(false);
2100 if($dependentPages) foreach($dependentPages as $page) {
2101
2102 $page->write();
2103 }
2104 Versioned::reading_stage($origStage);
2105
2106
2107 if ($virtualPages) foreach($virtualPages as $vp) $vp->doUnpublish();
2108
2109
2110
2111 if(DB::query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = $this->ID")->value()
2112 && Versioned::current_stage() != 'Live') {
2113 $this->Status = "Unpublished";
2114 $this->write();
2115 }
2116
2117 $this->extend('onAfterUnpublish');
2118
2119 return true;
2120 }
2121
2122 2123 2124 2125 2126 2127
2128 function doRollbackTo($version) {
2129 $this->publish($version, "Stage", true);
2130 $this->Status = "Saved (update)";
2131
2132 }
2133
2134 2135 2136
2137 function doRevertToLive() {
2138 $this->publish("Live", "Stage", false);
2139
2140
2141 $clone = DataObject::get_by_id("SiteTree", $this->ID);
2142 $clone->Status = "Published";
2143 $clone->writeWithoutVersion();
2144
2145
2146 foreach($this->DependentPages(false) as $page) {
2147
2148 $page->write();
2149 }
2150
2151 $this->extend('onAfterRevertToLive');
2152 }
2153
2154 2155 2156 2157
2158 function doRestoreToStage() {
2159
2160
2161 if(!DB::query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = $this->ID")->value()) {
2162 $conn = DB::getConn();
2163 if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('SiteTree', true);
2164 DB::query("INSERT INTO \"SiteTree\" (\"ID\") VALUES ($this->ID)");
2165 if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('SiteTree', false);
2166 }
2167
2168 $oldStage = Versioned::current_stage();
2169 Versioned::reading_stage('Stage');
2170 $this->forceChange();
2171 $this->writeWithoutVersion();
2172
2173 $result = DataObject::get_by_id($this->class, $this->ID);
2174
2175
2176 foreach($result->DependentPages(false) as $page) {
2177
2178 $page->write();
2179 }
2180
2181 Versioned::reading_stage($oldStage);
2182
2183 return $result;
2184 }
2185
2186 2187 2188
2189 function doDeleteFromLive() {
2190 return $this->doUnpublish();
2191 }
2192
2193 2194 2195 2196 2197 2198
2199 function isNew() {
2200 2201 2202 2203 2204 2205
2206 if(empty($this->ID)) return true;
2207
2208 if(is_numeric($this->ID)) return false;
2209
2210 return stripos($this->ID, 'new') === 0;
2211 }
2212
2213
2214 2215 2216 2217 2218
2219 function isPublished() {
2220 if($this->isNew())
2221 return false;
2222
2223 return (DB::query("SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"ID\" = $this->ID")->value())
2224 ? true
2225 : false;
2226 }
2227
2228 2229 2230 2231 2232 2233 2234
2235 protected function getClassDropdown() {
2236 $classes = self::page_type_classes();
2237 $currentClass = null;
2238 $result = array();
2239
2240 $result = array();
2241 foreach($classes as $class) {
2242 $instance = singleton($class);
2243 if((($instance instanceof HiddenClass) || !$instance->canCreate()) && ($class != $this->class)) continue;
2244
2245 $pageTypeName = $instance->i18n_singular_name();
2246
2247 if($class == $this->class) {
2248 $currentClass = $class;
2249 $result[$class] = $pageTypeName;
2250 } else {
2251 $translation = _t(
2252 'SiteTree.CHANGETO',
2253 'Change to "%s"',
2254 PR_MEDIUM,
2255 "Pagetype selection dropdown with class names"
2256 );
2257
2258
2259 if(strpos($translation, '%s') !== FALSE) {
2260 $result[$class] = sprintf(
2261 $translation,
2262 $pageTypeName
2263 );
2264 } else {
2265 $result[$class] = "{$translation} \"{$pageTypeName}\"";
2266 }
2267 }
2268
2269
2270
2271
2272
2273 if(i18n::get_locale() != 'en_US') {
2274 $result[$class] = $result[$class] . " ({$class})";
2275 }
2276 }
2277
2278
2279 asort($result);
2280 if($currentClass) {
2281 $currentPageTypeName = $result[$currentClass];
2282 unset($result[$currentClass]);
2283 $result = array_reverse($result);
2284 $result[$currentClass] = $currentPageTypeName;
2285 $result = array_reverse($result);
2286 }
2287
2288 return $result;
2289 }
2290
2291
2292 2293 2294 2295 2296 2297
2298 function allowedChildren() {
2299 $allowedChildren = array();
2300 $candidates = $this->stat('allowed_children');
2301 if($candidates && $candidates != "none" && $candidates != "SiteTree_root") {
2302 foreach($candidates as $candidate) {
2303 if(substr($candidate,0,1) == '*') {
2304 $allowedChildren[] = substr($candidate,1);
2305 } else {
2306 $subclasses = ClassInfo::subclassesFor($candidate);
2307 foreach($subclasses as $subclass) {
2308 if (ClassInfo::exists($subclass) && $subclass != "SiteTree_root") {
2309 $defParent = singleton($subclass)->defaultParent();
2310 if (!$defParent || ($defParent == $this->class)) {
2311 $allowedChildren[] = $subclass;
2312 }
2313 }
2314 }
2315 }
2316 }
2317 return $allowedChildren;
2318 }
2319 }
2320
2321
2322 2323 2324 2325 2326
2327 function defaultChild() {
2328 $default = $this->stat('default_child');
2329 $allowed = $this->allowedChildren();
2330 if($allowed) {
2331 if(!$default || !in_array($default, $allowed))
2332 $default = reset($allowed);
2333 return $default;
2334 }
2335 }
2336
2337
2338 2339 2340 2341 2342 2343
2344 function defaultParent() {
2345 return $this->stat('default_parent');
2346 }
2347
2348
2349 2350 2351 2352 2353
2354 function cmsCleanup_parentChanged() {
2355 }
2356
2357
2358 2359 2360 2361 2362 2363
2364 function (){
2365 if($value = $this->getField("MenuTitle")) {
2366 return $value;
2367 } else {
2368 return $this->getField("Title");
2369 }
2370 }
2371
2372
2373 2374 2375 2376 2377
2378 function ($value) {
2379 if($value == $this->getField("Title")) {
2380 $this->setField("MenuTitle", null);
2381 } else {
2382 $this->setField("MenuTitle", $value);
2383 }
2384 }
2385
2386 2387 2388 2389 2390 2391
2392 function TreeTitle() {
2393 if($this->IsDeletedFromStage) {
2394 if($this->ExistsOnLive) {
2395 $tag ="del title=\"" . _t('SiteTree.REMOVEDFROMDRAFT', 'Removed from draft site') . "\"";
2396 } else {
2397 $tag ="del class=\"deletedOnLive\" title=\"" . _t('SiteTree.DELETEDPAGE', 'Deleted page') . "\"";
2398 }
2399 } elseif($this->IsAddedToStage) {
2400 $tag = "ins title=\"" . _t('SiteTree.ADDEDTODRAFT', 'Added to draft site') . "\"";
2401 } elseif($this->IsModifiedOnStage) {
2402 $tag = "span title=\"" . _t('SiteTree.MODIFIEDONDRAFT', 'Modified on draft site') . "\" class=\"modified\"";
2403 } else {
2404 $tag = '';
2405 }
2406
2407 $text = Convert::raw2xml(str_replace(array("\n","\r"),"",$this->MenuTitle));
2408 return ($tag) ? "<$tag>" . $text . "</" . strtok($tag,' ') . ">" : $text;
2409 }
2410
2411 2412 2413 2414
2415 public function Level($level) {
2416 $parent = $this;
2417 $stack = array($parent);
2418 while($parent = $parent->Parent) {
2419 array_unshift($stack, $parent);
2420 }
2421
2422 return isset($stack[$level-1]) ? $stack[$level-1] : null;
2423 }
2424
2425 2426 2427 2428 2429 2430 2431
2432 function CMSTreeClasses($controller) {
2433 $classes = $this->class;
2434 if($this->HasBrokenFile || $this->HasBrokenLink)
2435 $classes .= " BrokenLink";
2436
2437 if(!$this->canAddChildren())
2438 $classes .= " nochildren";
2439
2440 if($controller->isCurrentPage($this))
2441 $classes .= " current";
2442
2443 if(!$this->canEdit() && !$this->canAddChildren())
2444 $classes .= " disabled";
2445
2446 if(!$this->ShowInMenus)
2447 $classes .= " notinmenu";
2448
2449
2450 2451 2452 2453
2454 $classes .= $this->markingClasses();
2455
2456 return $classes;
2457 }
2458
2459 2460 2461 2462 2463 2464 2465
2466 function getIsDeletedFromStage() {
2467 if(!$this->ID) return true;
2468 if($this->isNew()) return false;
2469
2470 $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
2471
2472
2473 return !($stageVersion);
2474 }
2475
2476 2477 2478
2479 function getExistsOnLive() {
2480 return (bool)Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
2481 }
2482
2483 2484 2485 2486 2487 2488 2489
2490 public function getIsModifiedOnStage() {
2491
2492 if($this->isNew()) return false;
2493
2494 $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
2495 $liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
2496
2497 return ($stageVersion != $liveVersion);
2498 }
2499
2500 2501 2502 2503 2504 2505 2506
2507 public function getIsAddedToStage() {
2508
2509 if($this->isNew()) return false;
2510
2511 $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
2512 $liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
2513
2514 return ($stageVersion && !$liveVersion);
2515 }
2516
2517 2518 2519 2520 2521 2522
2523 public static function disableCMSFieldsExtensions() {
2524 self::$runCMSFieldsExtensions = false;
2525 }
2526
2527 2528 2529 2530
2531 public static function enableCMSFieldsExtensions() {
2532 self::$runCMSFieldsExtensions = true;
2533 }
2534
2535 function providePermissions() {
2536 return array(
2537 'SITETREE_GRANT_ACCESS' => array(
2538 'name' => _t('SiteTree.PERMISSION_GRANTACCESS_DESCRIPTION', 'Manage access rights for content'),
2539 'help' => _t('SiteTree.PERMISSION_GRANTACCESS_HELP', 'Allow setting of page-specific access restrictions in the "Pages" section.'),
2540 'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
2541 'sort' => 100
2542 ),
2543 'SITETREE_VIEW_ALL' => array(
2544 'name' => _t('SiteTree.VIEW_ALL_DESCRIPTION', 'View any page'),
2545 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2546 'sort' => -100,
2547 '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')
2548 ),
2549 'SITETREE_EDIT_ALL' => array(
2550 'name' => _t('SiteTree.EDIT_ALL_DESCRIPTION', 'Edit any page'),
2551 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2552 'sort' => -50,
2553 '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')
2554 ),
2555 'SITETREE_REORGANISE' => array(
2556 'name' => _t('SiteTree.REORGANISE_DESCRIPTION', 'Change site structure'),
2557 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2558 'help' => _t('SiteTree.REORGANISE_HELP', 'Rearrange pages in the site tree through drag&drop.'),
2559 'sort' => 100
2560 ),
2561 'VIEW_DRAFT_CONTENT' => array(
2562 'name' => _t('SiteTree.VIEW_DRAFT_CONTENT', 'View draft content'),
2563 'category' => _t('Permissions.CONTENT_CATEGORY', 'Content permissions'),
2564 '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.'),
2565 'sort' => 100
2566 )
2567 );
2568 }
2569
2570 2571 2572 2573 2574
2575 function i18n_singular_name() {
2576 return _t($this->class.'.SINGULARNAME', $this->singular_name());
2577 }
2578
2579 2580 2581 2582
2583 function provideI18nEntities() {
2584 $entities = parent::provideI18nEntities();
2585
2586 if(isset($entities['Page.SINGULARNAME'])) $entities['Page.SINGULARNAME'][3] = 'sapphire';
2587 if(isset($entities['Page.PLURALNAME'])) $entities['Page.PLURALNAME'][3] = 'sapphire';
2588
2589 return $entities;
2590 }
2591
2592 function getParentType() {
2593 return $this->ParentID == 0 ? 'root' : 'subpage';
2594 }
2595
2596 static function reset() {
2597 self::$cache_permissions = array();
2598 }
2599
2600 }
2601
2602 ?>
2603
[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.
-