mirror of
https://github.com/silverstripe/silverstripe-cms
synced 2024-10-22 08:05:56 +02:00
API Make CMSMain more generic
Remove hardcoded references to pages and SiteTree Remove assumption that records are versioned Remove or validate assumptions about methods on the model class Improve general architecture of CMSMain
This commit is contained in:
parent
bd48b04731
commit
db3dcf7331
@ -1,8 +1,6 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Admin\CMSMenu;
|
||||
use SilverStripe\CMS\Controllers\CMSMain;
|
||||
use SilverStripe\CMS\Controllers\CMSPageAddController;
|
||||
use SilverStripe\CMS\Controllers\CMSPageEditController;
|
||||
use SilverStripe\CMS\Controllers\CMSPageSettingsController;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
@ -32,7 +30,5 @@ ShortcodeParser::get('default')->register(
|
||||
[SiteTree::class, 'link_shortcode_handler']
|
||||
);
|
||||
|
||||
CMSMenu::remove_menu_class(CMSMain::class);
|
||||
CMSMenu::remove_menu_class(CMSPageEditController::class);
|
||||
CMSMenu::remove_menu_class(CMSPageSettingsController::class);
|
||||
CMSMenu::remove_menu_class(CMSPageAddController::class);
|
||||
|
@ -2,4 +2,4 @@
|
||||
Name: cmsdefaultadmin
|
||||
---
|
||||
SilverStripe\Admin\AdminRootController:
|
||||
default_panel: SilverStripe\CMS\Controllers\CMSPagesController
|
||||
default_panel: SilverStripe\CMS\Controllers\CMSMain
|
||||
|
@ -4,10 +4,10 @@ After:
|
||||
- '#corecache'
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
Psr\SimpleCache\CacheInterface.CMSMain_SiteTreeHints:
|
||||
Psr\SimpleCache\CacheInterface.CMSMain_TreeHints:
|
||||
factory: SilverStripe\Core\Cache\CacheFactory
|
||||
constructor:
|
||||
namespace: "CMSMain_SiteTreeHints"
|
||||
namespace: "CMSMain_TreeHints"
|
||||
Psr\SimpleCache\CacheInterface.SiteTree_CreatableChildren:
|
||||
factory: SilverStripe\Core\Cache\CacheFactory
|
||||
constructor:
|
||||
|
@ -17,4 +17,3 @@ SilverStripe\Core\Injector\Injector:
|
||||
Services:
|
||||
- '%$SilverStripe\Security\PermissionChecker.sitetree'
|
||||
- '%$SilverStripe\CMS\Controllers\CMSMain'
|
||||
- '%$SilverStripe\CMS\Model\SiteTree'
|
||||
|
2
client/dist/js/bundle.js
vendored
2
client/dist/js/bundle.js
vendored
@ -1 +1 @@
|
||||
!function(){"use strict";var e={38:function(e,t,n){var a=i(n(420)),o=i(n(121));function i(e){return e&&e.__esModule?e:{default:e}}window.document.addEventListener("DOMContentLoaded",(()=>{(0,o.default)(),(0,a.default)()}))},121:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=i(n(207)),o=i(n(269));function i(e){return e&&e.__esModule?e:{default:e}}t.default=()=>{a.default.component.register("AnchorSelectorField",o.default)}},420:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=r(n(207)),o=n(367),i=r(n(796));function r(e){return e&&e.__esModule?e:{default:e}}t.default=()=>{a.default.reducer.register("cms",(0,o.combineReducers)({anchorSelector:i.default}))}},269:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.ConnectedAnchorSelectorField=t.Component=void 0;var a=C(n(815)),o=C(n(594)),i=C(n(950)),r=n(40),s=n(367),l=n(381),d=C(n(898)),u=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=_(t);if(n&&n.has(e))return n.get(e);var a={__proto__:null},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&{}.hasOwnProperty.call(e,i)){var r=o?Object.getOwnPropertyDescriptor(e,i):null;r&&(r.get||r.set)?Object.defineProperty(a,i,r):a[i]=e[i]}return a.default=e,n&&n.set(e,a),a}(n(979)),c=C(n(996)),f=C(n(623)),h=C(n(315)),p=C(n(657)),m=C(n(432)),g=C(n(304)),v=C(n(935));function _(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(_=function(e){return e?n:t})(e)}function C(e){return e&&e.__esModule?e:{default:e}}const b=()=>null;class w extends d.default{constructor(e){super(e),this.handleChange=this.handleChange.bind(this),this.handleLoadingError=this.handleLoadingError.bind(this)}componentDidMount(){this.ensurePagesLoaded()}componentDidUpdate(e){this.props.pageId!==e.pageId&&this.ensurePagesLoaded()}ensurePagesLoaded(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.props;if(e.loadingState===c.default.UPDATING||e.loadingState===c.default.SUCCESS||!e.pageId)return Promise.resolve();let t=[];e.loadingState===c.default.FIELD_ONLY&&(t=this.props.anchors),e.actions.anchorSelector.beginUpdating(e.pageId);const n=e.data.endpoint.replace(/:id/,e.pageId);return(0,i.default)(n,{credentials:"same-origin"}).then((e=>e.json())).then((n=>{const a=[...new Set([...n,...t])];return e.actions.anchorSelector.updated(e.pageId,a),a})).catch((t=>{e.actions.anchorSelector.updateFailed(e.pageId),this.handleLoadingError(t,e)}))}getDropdownOptions(){const e=this.props.anchors.map((e=>({value:e})));return this.props.value&&!this.props.anchors.find((e=>e===this.props.value))&&e.unshift({value:this.props.value}),e}handleChange(e){"function"==typeof this.props.onChange&&this.props.onChange(e?e.value:"")}handleLoadingError(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.props;if(t.onLoadingError===b)throw e;return t.onLoadingError({errors:[{value:e.message,type:"error"}]})}render(){const{extraClass:e,CreatableSelectComponent:t}=this.props,n=(0,g.default)("anchorselectorfield",e),i=this.getDropdownOptions(),r=this.props.value||"",s=a.default._t("CMS.ANCHOR_SELECT_OR_TYPE","Select or enter anchor");return o.default.createElement(p.default,null,o.default.createElement(t,{isSearchable:!0,isClearable:!0,options:i,className:n,name:this.props.name,onChange:this.handleChange,value:{value:r},noOptionsMessage:()=>a.default._t("CMS.ANCHOR_NO_OPTIONS","No options"),placeholder:s,getOptionLabel:e=>{let{value:t}=e;return t},classNamePrefix:"anchorselectorfield"}))}}t.Component=w,w.propTypes={extraClass:v.default.string,id:v.default.string,name:v.default.string.isRequired,onChange:v.default.func,value:v.default.string,attributes:v.default.oneOfType([v.default.object,v.default.array]),pageId:v.default.number,anchors:v.default.array,loadingState:v.default.oneOf(Object.keys(c.default).map((e=>c.default[e]))),onLoadingError:v.default.func,data:v.default.shape({endpoint:v.default.string,targetFieldName:v.default.string})},w.defaultProps={value:"",extraClass:"",onLoadingError:b,attributes:{},CreatableSelectComponent:h.default};const S=t.ConnectedAnchorSelectorField=(0,r.connect)((function(e,t){const n=(0,l.formValueSelector)(t.formid,m.default),a=t&&t.data&&t.data.targetFieldName||"PageID",o=Number(n(e,a)||0);let i=[];const r=o?e.cms.anchorSelector.pages.find((e=>e.id===o)):null;!r||r.loadingState!==c.default.SUCCESS&&r.loadingState!==c.default.DIRTY&&r.loadingState!==c.default.FIELD_ONLY||(i=r.anchors);let s=null;return s=r?r.loadingState:o?c.default.DIRTY:c.default.SUCCESS,{pageId:o,anchors:i,loadingState:s}}),(function(e){return{actions:{anchorSelector:(0,s.bindActionCreators)(u,e)}}}))(w);t.default=(0,f.default)(S)},586:function(e,t,n){var a;((a=n(669))&&a.__esModule?a:{default:a}).default.entwine("ss",(function(e){e(".TreeDropdownField").entwine({OldValue:null}),e("#Form_AddForm_ParentID_Holder .treedropdownfield").entwine({onmatch(){this._super(),e(".cms-add-form").updateTypeList()}}),e(".cms-add-form .parent-mode :input").entwine({onclick:function(e){var t=this.closest("form").find("#Form_AddForm_ParentID_Holder .TreeDropdownField");"top"==this.val()?(t.setOldValue(t.getValue()),t.setValue(0)):(t.setValue(t.getOldValue()||0),t.setOldValue(null)),t.refresh(),t.trigger("change")}}),e(".cms-add-form").entwine({ParentCache:{},onadd:function(){var t=this;this.find("#Form_AddForm_ParentID_Holder .TreeDropdownField").on("change",(function(){t.updateTypeList()})),this.find(".SelectionGroup.parent-mode").on("change",(function(){t.updateTypeList()})),"top"==e(".cms-add-form .parent-mode :input").val()&&this.updateTypeList()},loadCachedChildren:function(e){var t=this.getParentCache();return void 0!==t[e]?t[e]:null},saveCachedChildren:function(e,t){var n=this.getParentCache();n[e]=t,this.setParentCache(n)},updateTypeList:function(){var t=this.data("hints"),n=this.find("#Form_AddForm_ParentID"),a=this.find("input[name=ParentModeField]:checked").val(),o=n.data("metadata"),i="child"===a?n.getValue():null,r=o?o.ClassName:null,s=r&&"child"===a&&i?r:"Root",l=void 0!==t[s]?t[s]:null,d=this,u=l&&void 0!==l.defaultChild?l.defaultChild:null,c=[];if(i){if(this.hasClass("loading"))return;return this.addClass("loading"),null!==(c=this.loadCachedChildren(i))?(this.updateSelectionFilter(c,u),void this.removeClass("loading")):(e.ajax({url:d.data("childfilter"),data:{ParentID:i},success:function(e){d.saveCachedChildren(i,e),d.updateSelectionFilter(e,u)},complete:function(){d.removeClass("loading")}}),!1)}c=l&&void 0!==l.disallowedChildren?l.disallowedChildren:[],this.updateSelectionFilter(c,u)},updateSelectionFilter:function(t,n){var a=this.find("#Form_AddForm_PageType div.radio.selected")[0],o=!1,i=null;if(this.find("#Form_AddForm_PageType div.radio").each((function(n,r){var s=e(this).find("input").val(),l=-1===e.inArray(s,t);r===a&&l&&(o=!0),e(this).setEnabled(l),l||e(this).setSelected(!1),i=(null===i||i)&&l})),o)var r=e(a).parents("li:first");else if(n)r=this.find("#Form_AddForm_PageType div.radio input[value="+n+"]").parents("li:first");else r=this.find("#Form_AddForm_PageType div.radio:not(.disabled):first");r.setSelected(!0),r.siblings().setSelected(!1),this.find("#Form_AddForm_PageType div.radio:not(.disabled)").length?this.find("button[name=action_doAdd]").removeAttr("disabled"):this.find("button[name=action_doAdd]").attr("disabled","disabled"),this.find(".message-restricted")[i?"hide":"show"]()}}),e(".cms-add-form #Form_AddForm_PageType div.radio").entwine({onclick:function(e){this.setSelected(!0)},setSelected:function(e){var t=this.find("input");e&&!t.is(":disabled")?(this.siblings().setSelected(!1),this.toggleClass("selected",!0),t.prop("checked",!0)):(this.toggleClass("selected",!1),t.prop("checked",!1))},setEnabled:function(t){e(this).toggleClass("disabled",!t),t?e(this).find("input").removeAttr("disabled"):e(this).find("input").attr("disabled","disabled").removeAttr("checked")}}),e(".cms-content-addpage-button").entwine({onclick:function(t){var n,a=e(".cms-tree"),o=e(".cms-list"),i=0;if(a.is(":visible")){var r=a.jstree("get_selected");i=r?e(r[0]).data("id"):null}else{var s=o.find('input[name="Page[GridState]"]').val();s&&(i=parseInt(JSON.parse(s).ParentID,10))}var l,d={selector:this.data("targetPanel"),pjax:this.data("pjax")};i?(n=this.data("extraParams")?this.data("extraParams"):"",l=e.path.addSearchParams(i18n.sprintf(this.data("urlAddpage"),i),n)):l=this.attr("href"),e(".cms-container").loadPanel(l,null,d),t.preventDefault(),this.blur()}})}))},677:function(e,t,n){var a=r(n(669)),o=r(n(815)),i=r(n(216));function r(e){return e&&e.__esModule?e:{default:e}}a.default.entwine("ss",(function(e){e(".cms-edit-form :input#Form_EditForm_ClassName").entwine({onchange:function(){alert(o.default._t("CMS.ALERTCLASSNAME"))}}),e(".cms-edit-form input[name=Title]").entwine({onmatch:function(){var t=this;t.data("OrigVal",t.val());var n=t.closest("form"),a=e("input:text[name=URLSegment]",n),o=e("input[name=LiveLink]",n);a.length>0&&(t._addActions(),this.on("change",(function(n){var i=t.data("OrigVal"),r=t.val();t.data("OrigVal",r),0===a.val().indexOf(a.data("defaultUrl"))&&""==o.val()?t.updateURLSegment(r):e(".update",t.parent()).show().parent(".form__field-holder").addClass("input-group"),t.updateRelatedFields(r,i),t.updateBreadcrumbLabel(r)}))),this._super()},onunmatch:function(){this._super()},updateRelatedFields:function(t,n){this.parents("form").find("input[name=MetaTitle], input[name=MenuTitle]").each((function(){var a=e(this);a.val()==n&&(a.val(t),a.updatedRelatedFields&&a.updatedRelatedFields())}))},updateURLSegment:function(t){var n=e("input:text[name=URLSegment]",this.closest("form")).closest(".field.urlsegment"),a=e(".update",this.parent());n.update(t),a.is(":visible")&&a.hide().parent(".form__field-holder").removeClass("input-group")},updateBreadcrumbLabel:function(t){e(".cms-edit-form input[name=ID]").val();var n=e("span.cms-panel-link.crumb");t&&""!=t&&n.text(t)},_addActions:function(){var t,n=this;(t=e("<button />",{class:"update btn btn-outline-secondary form__field-update-url",text:o.default._t("CMS.UpdateURL"),type:"button",click:function(e){e.preventDefault(),n.updateURLSegment(n.val())}})).insertAfter(n),t.hide()}}),e(".cms-edit-form .parentTypeSelector").entwine({onmatch:function(){var e=this;this.find(":input[name=ParentType]").on("click",(function(t){e._toggleSelection(t)})),this.find(".TreeDropdownField").on("change",(function(t){e._changeParentId(t)})),this._changeParentId(),this._toggleSelection(),this._super()},onunmatch:function(){this._super()},_toggleSelection:function(t){var n=this.find(":input[name=ParentType]:checked").val(),a=this.find("#Form_EditForm_ParentID_Holder");"root"==n?this.find(":input[name=ParentID]").val(0):this.find(":input[name=ParentID]").val(this.find("#Form_EditForm_ParentType_subpage").data("parentIdValue")),"root"!=n?a.slideDown(400,(function(){e(this).css("overflow","visible")})):a.slideUp()},_changeParentId:function(e){var t=this.find(":input[name=ParentID]").val();this.find("#Form_EditForm_ParentType_subpage").data("parentIdValue",t)}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_doRollback, .cms-edit-form .btn-toolbar #Form_EditForm_action_rollback").entwine({onclick:function(e){if(this.is(":disabled"))return e.preventDefault(),!1;const t=this.parents("form:first").find(":input[name=Version]").val(),n=t?o.default.sprintf(o.default._t("CMS.RollbackToVersion","Do you really want to roll back to version #%s of this page?"),t):o.default._t("CMS.ConfirmRestoreFromLive","Are you sure you want to revert draft to when the page was last published?");return confirm(n)?(this.parents("form:first").addClass("loading"),this._super(e)):(e.preventDefault(),!1)}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_archive:not(.homepage-warning)").entwine({onclick:function(e){var t;return t=this.parents("form:first").find("input[name=ArchiveWarningMessage]").val().replace(/\\n/g,"\n"),!!confirm(t)&&(this.parents("form:first").addClass("loading"),this._super(e))}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_restore").entwine({onclick:function(e){var t,n=this.parents("form:first").find(":input[name=Version]").val(),a=this.data("toRoot");return t=o.default.sprintf(o.default._t(a?"CMS.RestoreToRoot":"CMS.Restore"),n),!!confirm(t)&&(this.parents("form:first").addClass("loading"),this._super(e))}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_unpublish:not(.homepage-warning)").entwine({onclick:function(e){var t,n=this.parents("form:first").find(":input[name=Version]").val();return t=o.default.sprintf(o.default._t("CMS.Unpublish"),n),!!confirm(t)&&(this.parents("form:first").addClass("loading"),this._super(e))}}),e(".cms-edit-form.changed").entwine({onmatch:function(t){this.find("button[data-text-alternate]").each((function(){const t=e(this),n=t.find(".btn__title"),a=t.data("textAlternate");a&&(t.data("textStandard",n.text()),n.text(a));const o=t.data("btnAlternate");o&&(t.data("btnStandard",t.attr("class")),t.attr("class",o),t.removeClass("btn-outline-secondary").addClass("btn-primary"));const i=t.data("btnAlternateAdd");i&&t.addClass(i);const r=t.data("btnAlternateRemove");r&&t.removeClass(r)})),this._super(t)},onunmatch:function(t){this.find("button[data-text-alternate]").each((function(){const t=e(this),n=t.find(".btn__title"),a=t.data("textStandard");a&&n.text(a);const o=t.data("btnStandard");o&&(t.attr("class",o),t.addClass("btn-outline-secondary").removeClass("btn-primary"));const i=t.data("btnAlternateAdd");i&&t.removeClass(i);const r=t.data("btnAlternateRemove");r&&t.addClass(r)})),this._super(t)}}),e(".cms-edit-form .btn-toolbar button[name=action_publish]").entwine({onbuttonafterrefreshalternate:function(){this.data("showingAlternate")?(this.addClass("btn-primary"),this.removeClass("btn-secondary")):(this.removeClass("btn-primary"),this.addClass("btn-secondary"))}}),e(".cms-edit-form .btn-toolbar button[name=action_save]").entwine({onbuttonafterrefreshalternate:function(){this.data("showingAlternate")?(this.addClass("btn-primary"),this.removeClass("btn-secondary")):(this.removeClass("btn-primary"),this.addClass("btn-secondary"))}}),e('.cms-edit-form.CMSPageSettingsController input[name="ParentType"]:checked').entwine({onmatch:function(){this.redraw(),this._super()},onunmatch:function(){this._super()},redraw:function(){var t=e(".cms-edit-form.CMSPageSettingsController #Form_EditForm_ParentID_Holder");"Form_EditForm_ParentType_root"==e(this).attr("id")?t.slideUp():t.slideDown()},onclick:function(){this.redraw()}}),"Form_EditForm_ParentType_root"==e('.cms-edit-form.CMSPageSettingsController input[name="ParentType"]:checked').attr("id")&&e(".cms-edit-form.CMSPageSettingsController #Form_EditForm_ParentID_Holder").hide();var t=!1;e(".cms-edit-form .btn-toolbar #Form_EditForm_action_unpublish.homepage-warning,.cms-edit-form .btn-toolbar #Form_EditForm_action_archive.homepage-warning,#Form_EditForm_URLSegment_Holder.homepage-warning .btn.update").entwine({onclick:async function(e){if(t)return this._super(e);e.stopPropagation();var n=o.default._t("CMS.RemoveHomePageWarningMessage","Warning: This page is the home page. By changing the URL segment visitors will not be able to view it.");return await(0,i.default)({title:o.default._t("CMS.RemoveHomePageWarningTitle","Remove your home page?"),message:n,confirmText:o.default._t("CMS.RemoveHomePageWarningLabel","Remove"),confirmColor:"danger"})&&(t=!0,this.trigger("click"),t=!1),!1}})}))},55:function(e,t,n){var a=s(n(669)),o=s(n(815)),i=s(n(216)),r=n(125);function s(e){return e&&e.__esModule?e:{default:e}}a.default.entwine("ss.tree",(function(e){e(".cms-tree").entwine({fromDocument:{"oncontext_show.vakata":function(e){this.adjustContextClass()}},adjustContextClass:function(){var t=e("#vakata-contextmenu").find("ul ul");t.each((function(n){var a="1",o=e(t[n]).find("li").length;o>20?a="3":o>10&&(a="2"),e(t[n]).addClass("vakata-col-"+a).removeClass("right"),e(t[n]).find("li").on("mouseenter",(function(t){e(this).parent("ul").removeClass("right")}))}))},showListViewFor:function(t){localStorage.setItem("ss.pages-view-type","listview");const n=this.closest(".cms-content-view").data("url-listviewroot"),a=e.path.addSearchParams(n,{ParentID:t}),o=e("base").attr("href")||"";window.location.assign((0,r.joinUrlPaths)(o,a))},getTreeConfig:function(){var t=this,n=this._super();this.getHints();return n.plugins.push("contextmenu"),n.contextmenu={items:function(n){var a={edit:{label:n.hasClass("edit-disabled")?o.default._t("CMS.EditPage","Edit page",100,"Used in the context menu when right-clicking on a page node in the CMS tree"):o.default._t("CMS.ViewPage","View page",100,"Used in the context menu when right-clicking on a page node in the CMS tree"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(o.default.sprintf(t.data("urlEditpage"),n.data("id")))}}};n.hasClass("nochildren")||(a.showaslist={label:o.default._t("CMS.ShowAsList"),action:function(e){t.showListViewFor(e.data("id"))}});n.data("pagetype");var i=n.data("id"),r=n.find(">a .item").data("allowedchildren"),s={},l=!1;return e.each(r,(function(n,a){l=!0,s["allowedchildren-"+a.ClassName]={label:'<span class="jstree-pageicon '+a.IconClass+'"></span>'+a.Title,_class:"class-"+a.ClassName.replace(/[^a-zA-Z0-9\-_:.]+/g,"_"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(e.path.addSearchParams(o.default.sprintf(t.data("urlAddpage"),i,a.ClassName),t.data("extraParams")))}}})),l&&(a.addsubpage={label:o.default._t("CMS.AddSubPage","Add page under this page",100,"Used in the context menu when right-clicking on a page node in the CMS tree"),submenu:s}),n.hasClass("edit-disabled")||(a.duplicate={label:o.default._t("CMS.Duplicate"),submenu:[{label:o.default._t("CMS.ThisPageOnly"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(e.path.addSearchParams(o.default.sprintf(t.data("urlDuplicate"),n.data("id")),t.data("extraParams")))}},{label:o.default._t("CMS.ThisPageAndSubpages"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(e.path.addSearchParams(o.default.sprintf(t.data("urlDuplicatewithchildren"),n.data("id")),t.data("extraParams")))}}]}),a}},n},canMove:async function(e){if(!(e.rslt.o.find(".homepage").first().length>0))return!0;if(e.rslt.op.data("id")===e.rslt.np.data("id"))return!0;var t=o.default._t("CMS.RemoveHomePageWarningMessage","Warning: This page is the home page. By changing the URL segment visitors will not be able to view it.");return await(0,i.default)({title:o.default._t("CMS.RemoveHomePageWarningTitle","Remove your home page?"),message:t,confirmText:o.default._t("CMS.RemoveHomePageWarningLabel","Remove"),confirmColor:"danger"})}}),e(".cms-tree a.jstree-clicked").entwine({onmatch:function(){var e=this,t=e.parents(".cms-tree-view-sidebar");if(e.offset().top<0||e.offset().top>t.height()-e.height()){var n=e.parent();n.prev().length&&(n=n.prev()),n.get(0).scrollIntoView()}}}),e(".cms-tree-filtered .clear-filter").entwine({onclick:function(){window.location=location.protocol+"//"+location.host+location.pathname}}),e(".cms-tree .subtree-list-link").entwine({onclick:function(e){e.preventDefault(),this.closest(".cms-tree").showListViewFor(this.data("id"))}})}))},881:function(e,t,n){var a,o=(a=n(669))&&a.__esModule?a:{default:a},i=n(125);o.default.entwine("ss",(function(e){const t="treeview",n="listview";e(".cms-content-header-info").entwine({"from .cms-panel":{ontoggle:function(e){var t=this.closest(".cms-content").find(e.target);0!==t.length&&this.parent()[t.hasClass("collapsed")?"addClass":"removeClass"]("collapsed")}}}),e(".cms-panel-deferred.cms-content-view").entwine({onadd:function(){if(this.data("no-ajax"))return;var e=localStorage.getItem("ss.pages-view-type")||t;this.closest(".cms-content-tools").length>0&&(e=t);const a=this.data(`url-${e}`);let o=localStorage.getItem("ss.pages-view-filtered");"string"==typeof o&&"false"===o.toLowerCase()&&(o=!1),localStorage.setItem("ss.pages-view-filtered",!1),this.data("deferredNoCache",o||e===n),this.data("url",a+location.search),this._super()}}),e(".js-injector-boot .search-holder--cms").entwine({search(e){localStorage.setItem("ss.pages-view-filtered",!0),this._super(e)}}),e(".cms .page-view-link").entwine({onclick:function(t){t.preventDefault();const a=e(this).data("view"),o=this.closest(".cms-content-view"),r=o.data(`url-${a}`),s=0!==o.closest(".cms-content-tools").length;if(localStorage.setItem("ss.pages-view-type",a),s&&a===n){const t=e("base").attr("href")||"";window.location.assign((0,i.joinUrlPaths)(t,o.data("url-listviewroot")))}else o.data("url",r+location.search),o.redraw()}}),e(".cms .cms-clear-filter").entwine({onclick:function(t){t.preventDefault(),window.location=e(this).prop("href")}}),e(".cms-content-toolbar").entwine({onmatch:function(){var t=this;this._super(),e.each(this.find(".cms-actions-buttons-row .tool-button"),(function(){var n=e(this),a=n.data("toolid");n.hasClass("active");void 0!==a&&(n.data("active",!1).removeClass("active"),e("#"+a).hide(),t.bindActionButtonEvents(n))}))},onunmatch:function(){var t=this;this._super(),e.each(this.find(".cms-actions-buttons-row .tool-button"),(function(){var n=e(this);t.unbindActionButtonEvents(n)}))},bindActionButtonEvents:function(e){var t=this;e.on("click.cmsContentToolbar",(function(n){t.showHideTool(e)}))},unbindActionButtonEvents:function(e){e.off(".cmsContentToolbar")},showHideTool:function(t){var n=t.data("active"),a=t.data("toolid"),o=e("#"+a);e.each(this.find(".cms-actions-buttons-row .tool-button"),(function(){var t=e(this),n=e("#"+t.data("toolid"));t.data("toolid")!==a&&(n.hide(),t.data("active",!1))})),t[n?"removeClass":"addClass"]("active"),o[n?"hide":"show"](),t.data("active",!n)}})}))},739:function(e,t,n){var a;((a=n(669))&&a.__esModule?a:{default:a}).default.entwine("ss",(function(e){e("#Form_EditForm_RedirectionType input").entwine({onmatch:function(){e(this).attr("checked")&&this.toggle(),this._super()},onunmatch:function(){this._super()},onclick:function(){this.toggle()},toggle:function(){"Internal"==e(this).attr("value")?(e("#Form_EditForm_ExternalURL_Holder").hide(),e("#Form_EditForm_LinkToID_Holder").show(),e("#Form_EditForm_LinkToFile_Holder").hide()):"External"==e(this).attr("value")?(e("#Form_EditForm_ExternalURL_Holder").show(),e("#Form_EditForm_LinkToID_Holder").hide(),e("#Form_EditForm_LinkToFile_Holder").hide()):(e("#Form_EditForm_LinkToFile_Holder").show(),e("#Form_EditForm_ExternalURL_Holder").hide(),e("#Form_EditForm_LinkToID_Holder").hide())}})}))},978:function(e,t,n){var a;((a=n(669))&&a.__esModule?a:{default:a}).default.entwine("ss",(function(e){e(".field.urlsegment:not(.readonly)").entwine({MaxPreviewLength:55,Ellipsis:"...",onmatch:function(){this.find(":text").length&&this.toggleEdit(!1),this.redraw(),this._super()},redraw:function(){var e=this.find(":text"),t=decodeURI(e.data("prefix")+e.val()),n=t;t.length>this.getMaxPreviewLength()&&(n=this.getEllipsis()+t.substr(t.length-this.getMaxPreviewLength(),t.length)),this.find(".URL-link").attr("href",encodeURI(t+e.data("suffix"))).text(n)},toggleEdit:function(e){var t=this.find(":text");this.find(".preview-holder")[e?"hide":"show"](),this.find(".edit-holder")[e?"show":"hide"](),e&&(t.data("origval",t.val()),t.focus())},update:function(){var e=this,t=this.find(":text"),n=t.data("origval"),a=arguments[0],o=a&&""!==a?a:t.val();n!=o?(this.addClass("loading"),this.suggest(o,(function(n){t.val(decodeURIComponent(n.value)),e.toggleEdit(!1),e.removeClass("loading"),e.redraw()}))):(this.toggleEdit(!1),this.redraw())},cancel:function(){var e=this.find(":text");e.val(e.data("origval")),this.toggleEdit(!1)},suggest:function(t,n){var a=this,o=a.find(":text"),i=e.path.parseUrl(a.closest("form").attr("action")),r=i.hrefNoSearch+"/field/"+o.attr("name")+"/suggest/?value="+encodeURIComponent(t);i.search&&(r+="&"+i.search.replace(/^\?/,"")),e.ajax({url:r,success:function(e){n.apply(this,arguments)},error:function(e,t){e.statusText=e.responseText},complete:function(){a.removeClass("loading")}})}}),e(".field.urlsegment .text").entwine({onkeydown:function(e){13===e.keyCode&&(e.preventDefault(),this.closest(".field").update())}}),e(".field.urlsegment .edit").entwine({onclick:function(e){e.preventDefault(),this.closest(".field").toggleEdit(!0)}}),e(".field.urlsegment .update").entwine({onclick:function(e){e.preventDefault(),this.closest(".field").update()}}),e(".field.urlsegment .cancel").entwine({onclick:function(e){e.preventDefault(),this.closest(".field").cancel()}})}))},803:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;t.default={ANCHORSELECTOR_CURRENT_FIELD:"ANCHORSELECTOR_CURRENT_FIELD",ANCHORSELECTOR_UPDATED:"ANCHORSELECTOR_UPDATED",ANCHORSELECTOR_UPDATING:"ANCHORSELECTOR_UPDATING",ANCHORSELECTOR_UPDATE_FAILED:"ANCHORSELECTOR_UPDATE_FAILED"}},979:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.beginUpdating=function(e){return{type:o.default.ANCHORSELECTOR_UPDATING,payload:{pageId:e}}},t.updateFailed=function(e){return{type:o.default.ANCHORSELECTOR_UPDATE_FAILED,payload:{pageId:e}}},t.updated=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return{type:o.default.ANCHORSELECTOR_UPDATED,payload:{pageId:e,anchors:t,cacheResult:n}}},t.updatedCurrentField=function(e,t,n){return{type:o.default.ANCHORSELECTOR_CURRENT_FIELD,payload:{pageId:e,anchors:t,fieldID:n}}};var a,o=(a=n(803))&&a.__esModule?a:{default:a}},796:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:s,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;const n=(n,o)=>{const i=t.payload.pageId;return(0,a.default)({pages:[...e.pages.filter((e=>e.id!==i)),{id:i,loadingState:n,anchors:o}].sort(((e,t)=>e.id-t.id))})};switch(t.type){case o.default.ANCHORSELECTOR_UPDATING:return n(i.default.UPDATING,[]);case o.default.ANCHORSELECTOR_UPDATED:{const{anchors:e,cacheResult:a}=t.payload,{SUCCESS:o,DIRTY:r}=i.default;return n(a?o:r,e)}case o.default.ANCHORSELECTOR_CURRENT_FIELD:{const{anchors:e}=t.payload;return n(i.default.FIELD_ONLY,e)}case o.default.ANCHORSELECTOR_UPDATE_FAILED:return n(i.default.FAILED,[]);default:return e}};var a=r(n(923)),o=r(n(803)),i=r(n(996));function r(e){return e&&e.__esModule?e:{default:e}}const s=(0,a.default)({pages:[]})},996:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;t.default={SUCCESS:"SUCCESS",DIRTY:"DIRTY",FIELD_ONLY:"FIELD_ONLY",UPDATING:"UPDATING",FAILED:"FAILED"}},560:function(e,t,n){function a(e){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},a(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o,i=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!==a(e)&&"function"!=typeof e)return{default:e};var n=l(t);if(n&&n.has(e))return n.get(e);var o={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var r in e)if("default"!==r&&Object.prototype.hasOwnProperty.call(e,r)){var s=i?Object.getOwnPropertyDescriptor(e,r):null;s&&(s.get||s.set)?Object.defineProperty(o,r,s):o[r]=e[r]}o.default=e,n&&n.set(e,o);return o}(n(594)),r=(o=n(935))&&o.__esModule?o:{default:o},s=n(556);function l(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(l=function(e){return e?n:t})(e)}function d(){return d=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(e[a]=n[a])}return e},d.apply(this,arguments)}var u=function(e){var t=e.onClose,n=e.message,a=e.title,o=e.confirmText,r=e.cancelText,l=e.confirmColor,u=e.cancelColor,c=e.className,f=e.buttonsComponent,h=e.size,p=e.bodyComponent,m=e.modalProps,g=i.default.createElement(i.Fragment,null,r&&i.default.createElement(s.Button,{color:u,onClick:function(){return t(!1)}},r)," ",i.default.createElement(s.Button,{color:l,onClick:function(){return t(!0)}},o));if(f){var v=f;g=i.default.createElement(v,{onClose:t})}var _=p;return i.default.createElement(s.Modal,d({size:h,isOpen:!0,toggle:function(){return t(!1)},className:"reactstrap-confirm ".concat(c)},m),a&&i.default.createElement(s.ModalHeader,{toggle:function(){return t(!1)}},a||null),i.default.createElement(s.ModalBody,null,p?i.default.createElement(_,null):n),i.default.createElement(s.ModalFooter,null,g))};u.defaultProps={message:"Are you sure?",title:"Warning!",confirmText:"Ok",cancelText:"Cancel",confirmColor:"primary",cancelColor:"",className:"",buttonsComponent:null,size:null,bodyComponent:null,modalProps:{}},u.propTypes={onClose:r.default.func.isRequired,message:r.default.node,title:r.default.node,confirmText:r.default.node,cancelText:r.default.node,confirmColor:r.default.string,cancelColor:r.default.string,className:r.default.string,size:r.default.string,buttonsComponent:r.default.func,bodyComponent:r.default.func,modalProps:r.default.object};var c=u;t.default=c},216:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=r(n(594)),o=n(518),i=r(n(560));function r(e){return e&&e.__esModule?e:{default:e}}function s(){return s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(e[a]=n[a])}return e},s.apply(this,arguments)}var l=function(e){return new Promise((function(t){var n=document.createElement("div");(0,o.render)(a.default.createElement(i.default,s({},e,{onClose:function(e){(0,o.unmountComponentAtNode)(n),n=null,t(e)}})),n)}))};t.default=l},923:function(e){e.exports=DeepFreezeStrict},657:function(e){e.exports=EmotionCssCacheProvider},623:function(e){e.exports=FieldHolder},207:function(e){e.exports=Injector},950:function(e){e.exports=IsomorphicFetch},935:function(e){e.exports=PropTypes},594:function(e){e.exports=React},518:function(e){e.exports=ReactDom},40:function(e){e.exports=ReactRedux},315:function(e){e.exports=ReactSelectCreatable},556:function(e){e.exports=Reactstrap},367:function(e){e.exports=Redux},381:function(e){e.exports=ReduxForm},898:function(e){e.exports=SilverStripeComponent},304:function(e){e.exports=classnames},432:function(e){e.exports=getFormState},815:function(e){e.exports=i18n},669:function(e){e.exports=jQuery},125:function(e){e.exports=ssUrlLib}},t={};function n(a){var o=t[a];if(void 0!==o)return o.exports;var i=t[a]={exports:{}};return e[a](i,i.exports,n),i.exports}n(586),n(677),n(881),n(55),n(739),n(978),n(38)}();
|
||||
!function(){"use strict";var e={38:function(e,t,n){var a=i(n(420)),o=i(n(121));function i(e){return e&&e.__esModule?e:{default:e}}window.document.addEventListener("DOMContentLoaded",(()=>{(0,o.default)(),(0,a.default)()}))},121:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=i(n(207)),o=i(n(269));function i(e){return e&&e.__esModule?e:{default:e}}t.default=()=>{a.default.component.register("AnchorSelectorField",o.default)}},420:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=r(n(207)),o=n(367),i=r(n(796));function r(e){return e&&e.__esModule?e:{default:e}}t.default=()=>{a.default.reducer.register("cms",(0,o.combineReducers)({anchorSelector:i.default}))}},269:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.ConnectedAnchorSelectorField=t.Component=void 0;var a=C(n(815)),o=C(n(594)),i=C(n(950)),r=n(40),s=n(367),l=n(381),d=C(n(898)),c=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=_(t);if(n&&n.has(e))return n.get(e);var a={__proto__:null},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&{}.hasOwnProperty.call(e,i)){var r=o?Object.getOwnPropertyDescriptor(e,i):null;r&&(r.get||r.set)?Object.defineProperty(a,i,r):a[i]=e[i]}return a.default=e,n&&n.set(e,a),a}(n(979)),u=C(n(996)),f=C(n(623)),h=C(n(315)),p=C(n(657)),m=C(n(432)),g=C(n(304)),v=C(n(935));function _(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(_=function(e){return e?n:t})(e)}function C(e){return e&&e.__esModule?e:{default:e}}const b=()=>null;class w extends d.default{constructor(e){super(e),this.handleChange=this.handleChange.bind(this),this.handleLoadingError=this.handleLoadingError.bind(this)}componentDidMount(){this.ensurePagesLoaded()}componentDidUpdate(e){this.props.pageId!==e.pageId&&this.ensurePagesLoaded()}ensurePagesLoaded(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.props;if(e.loadingState===u.default.UPDATING||e.loadingState===u.default.SUCCESS||!e.pageId)return Promise.resolve();let t=[];e.loadingState===u.default.FIELD_ONLY&&(t=this.props.anchors),e.actions.anchorSelector.beginUpdating(e.pageId);const n=e.data.endpoint.replace(/:id/,e.pageId);return(0,i.default)(n,{credentials:"same-origin"}).then((e=>e.json())).then((n=>{const a=[...new Set([...n,...t])];return e.actions.anchorSelector.updated(e.pageId,a),a})).catch((t=>{e.actions.anchorSelector.updateFailed(e.pageId),this.handleLoadingError(t,e)}))}getDropdownOptions(){const e=this.props.anchors.map((e=>({value:e})));return this.props.value&&!this.props.anchors.find((e=>e===this.props.value))&&e.unshift({value:this.props.value}),e}handleChange(e){"function"==typeof this.props.onChange&&this.props.onChange(e?e.value:"")}handleLoadingError(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.props;if(t.onLoadingError===b)throw e;return t.onLoadingError({errors:[{value:e.message,type:"error"}]})}render(){const{extraClass:e,CreatableSelectComponent:t}=this.props,n=(0,g.default)("anchorselectorfield",e),i=this.getDropdownOptions(),r=this.props.value||"",s=a.default._t("CMS.ANCHOR_SELECT_OR_TYPE","Select or enter anchor");return o.default.createElement(p.default,null,o.default.createElement(t,{isSearchable:!0,isClearable:!0,options:i,className:n,name:this.props.name,onChange:this.handleChange,value:{value:r},noOptionsMessage:()=>a.default._t("CMS.ANCHOR_NO_OPTIONS","No options"),placeholder:s,getOptionLabel:e=>{let{value:t}=e;return t},classNamePrefix:"anchorselectorfield"}))}}t.Component=w,w.propTypes={extraClass:v.default.string,id:v.default.string,name:v.default.string.isRequired,onChange:v.default.func,value:v.default.string,attributes:v.default.oneOfType([v.default.object,v.default.array]),pageId:v.default.number,anchors:v.default.array,loadingState:v.default.oneOf(Object.keys(u.default).map((e=>u.default[e]))),onLoadingError:v.default.func,data:v.default.shape({endpoint:v.default.string,targetFieldName:v.default.string})},w.defaultProps={value:"",extraClass:"",onLoadingError:b,attributes:{},CreatableSelectComponent:h.default};const S=t.ConnectedAnchorSelectorField=(0,r.connect)((function(e,t){const n=(0,l.formValueSelector)(t.formid,m.default),a=t&&t.data&&t.data.targetFieldName||"PageID",o=Number(n(e,a)||0);let i=[];const r=o?e.cms.anchorSelector.pages.find((e=>e.id===o)):null;!r||r.loadingState!==u.default.SUCCESS&&r.loadingState!==u.default.DIRTY&&r.loadingState!==u.default.FIELD_ONLY||(i=r.anchors);let s=null;return s=r?r.loadingState:o?u.default.DIRTY:u.default.SUCCESS,{pageId:o,anchors:i,loadingState:s}}),(function(e){return{actions:{anchorSelector:(0,s.bindActionCreators)(c,e)}}}))(w);t.default=(0,f.default)(S)},586:function(e,t,n){var a;((a=n(669))&&a.__esModule?a:{default:a}).default.entwine("ss",(function(e){e(".TreeDropdownField").entwine({OldValue:null}),e("#Form_AddForm_ParentID_Holder .treedropdownfield").entwine({onmatch(){this._super(),e(".cms-add-form").updateTypeList()}}),e(".cms-add-form .parent-mode :input").entwine({onclick:function(e){var t=this.closest("form").find("#Form_AddForm_ParentID_Holder .TreeDropdownField");"top"==this.val()?(t.setOldValue(t.getValue()),t.setValue(0)):(t.setValue(t.getOldValue()||0),t.setOldValue(null)),t.refresh(),t.trigger("change")}}),e(".cms-add-form").entwine({ParentCache:{},onadd:function(){var t=this;this.find("#Form_AddForm_ParentID_Holder .TreeDropdownField").on("change",(function(){t.updateTypeList()})),this.find(".SelectionGroup.parent-mode").on("change",(function(){t.updateTypeList()})),"top"==e(".cms-add-form .parent-mode :input").val()&&this.updateTypeList()},loadCachedChildren:function(e){var t=this.getParentCache();return void 0!==t[e]?t[e]:null},saveCachedChildren:function(e,t){var n=this.getParentCache();n[e]=t,this.setParentCache(n)},updateTypeList:function(){var t=this.data("hints"),n=this.find("#Form_AddForm_ParentID"),a=this.find("input[name=ParentModeField]:checked").val(),o=n.data("metadata"),i="child"===a?n.getValue():null,r=o?o.ClassName:null,s=r&&"child"===a&&i?r:"Root",l=void 0!==t[s]?t[s]:null,d=this,c=l&&void 0!==l.defaultChild?l.defaultChild:null,u=[];if(i){if(this.hasClass("loading"))return;return this.addClass("loading"),null!==(u=this.loadCachedChildren(i))?(this.updateSelectionFilter(u,c),void this.removeClass("loading")):(e.ajax({url:d.data("childfilter"),data:{ParentID:i},success:function(e){d.saveCachedChildren(i,e),d.updateSelectionFilter(e,c)},complete:function(){d.removeClass("loading")}}),!1)}u=l&&void 0!==l.disallowedChildren?l.disallowedChildren:[],this.updateSelectionFilter(u,c)},updateSelectionFilter:function(t,n){var a=this.find("#Form_AddForm_RecordType div.radio.selected")[0],o=!1,i=null;if(this.find("#Form_AddForm_RecordType div.radio").each((function(n,r){var s=e(this).find("input").val(),l=-1===e.inArray(s,t);r===a&&l&&(o=!0),e(this).setEnabled(l),l||e(this).setSelected(!1),i=(null===i||i)&&l})),o)var r=e(a).parents("li:first");else if(n)r=this.find("#Form_AddForm_RecordType div.radio input[value="+n+"]").parents("li:first");else r=this.find("#Form_AddForm_RecordType div.radio:not(.disabled):first");r.setSelected(!0),r.siblings().setSelected(!1),this.find("#Form_AddForm_RecordType div.radio:not(.disabled)").length?this.find("button[name=action_doAdd]").removeAttr("disabled"):this.find("button[name=action_doAdd]").attr("disabled","disabled"),this.find(".message-restricted")[i?"hide":"show"]()}}),e(".cms-add-form #Form_AddForm_RecordType div.radio").entwine({onclick:function(e){this.setSelected(!0)},setSelected:function(e){var t=this.find("input");e&&!t.is(":disabled")?(this.siblings().setSelected(!1),this.toggleClass("selected",!0),t.prop("checked",!0)):(this.toggleClass("selected",!1),t.prop("checked",!1))},setEnabled:function(t){e(this).toggleClass("disabled",!t),t?e(this).find("input").removeAttr("disabled"):e(this).find("input").attr("disabled","disabled").removeAttr("checked")}}),e(".cms-content-addpage-button").entwine({onclick:function(t){var n,a=e(".cms-tree"),o=e(".cms-list"),i=0;if(a.is(":visible")){var r=a.jstree("get_selected");i=r?e(r[0]).data("id"):null}else{var s=o.find('input[name="Page[GridState]"]').val();s&&(i=parseInt(JSON.parse(s).ParentID,10))}var l,d={selector:this.data("targetPanel"),pjax:this.data("pjax")};i?(n=this.data("extraParams")?this.data("extraParams"):"",l=e.path.addSearchParams(i18n.sprintf(this.data("urlAddpage"),i),n)):l=this.attr("href"),e(".cms-container").loadPanel(l,null,d),t.preventDefault(),this.blur()}})}))},677:function(e,t,n){var a=r(n(669)),o=r(n(815)),i=r(n(216));function r(e){return e&&e.__esModule?e:{default:e}}a.default.entwine("ss",(function(e){e(".cms-edit-form :input#Form_EditForm_ClassName").entwine({onchange:function(){alert(o.default._t("CMS.ALERTCLASSNAME"))}}),e(".cms-edit-form input[name=Title]").entwine({onmatch:function(){var t=this;t.data("OrigVal",t.val());var n=t.closest("form"),a=e("input:text[name=URLSegment]",n),o=e("input[name=LiveLink]",n);a.length>0&&(t._addActions(),this.on("change",(function(n){var i=t.data("OrigVal"),r=t.val();t.data("OrigVal",r),0===a.val().indexOf(a.data("defaultUrl"))&&""==o.val()?t.updateURLSegment(r):e(".update",t.parent()).show().parent(".form__field-holder").addClass("input-group"),t.updateRelatedFields(r,i),t.updateBreadcrumbLabel(r)}))),this._super()},onunmatch:function(){this._super()},updateRelatedFields:function(t,n){this.parents("form").find("input[name=MetaTitle], input[name=MenuTitle]").each((function(){var a=e(this);a.val()==n&&(a.val(t),a.updatedRelatedFields&&a.updatedRelatedFields())}))},updateURLSegment:function(t){var n=e("input:text[name=URLSegment]",this.closest("form")).closest(".field.urlsegment"),a=e(".update",this.parent());n.update(t),a.is(":visible")&&a.hide().parent(".form__field-holder").removeClass("input-group")},updateBreadcrumbLabel:function(t){e(".cms-edit-form input[name=ID]").val();var n=e("span.cms-panel-link.crumb");t&&""!=t&&n.text(t)},_addActions:function(){var t,n=this;(t=e("<button />",{class:"update btn btn-outline-secondary form__field-update-url",text:o.default._t("CMS.UpdateURL"),type:"button",click:function(e){e.preventDefault(),n.updateURLSegment(n.val())}})).insertAfter(n),t.hide()}}),e(".cms-edit-form .parentTypeSelector").entwine({onmatch:function(){var e=this;this.find(":input[name=ParentType]").on("click",(function(t){e._toggleSelection(t)})),this.find(".TreeDropdownField").on("change",(function(t){e._changeParentId(t)})),this._changeParentId(),this._toggleSelection(),this._super()},onunmatch:function(){this._super()},_toggleSelection:function(t){var n=this.find(":input[name=ParentType]:checked").val(),a=this.find("#Form_EditForm_ParentID_Holder");"root"==n?this.find(":input[name=ParentID]").val(0):this.find(":input[name=ParentID]").val(this.find("#Form_EditForm_ParentType_subpage").data("parentIdValue")),"root"!=n?a.slideDown(400,(function(){e(this).css("overflow","visible")})):a.slideUp()},_changeParentId:function(e){var t=this.find(":input[name=ParentID]").val();this.find("#Form_EditForm_ParentType_subpage").data("parentIdValue",t)}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_doRollback, .cms-edit-form .btn-toolbar #Form_EditForm_action_rollback").entwine({onclick:function(e){if(this.is(":disabled"))return e.preventDefault(),!1;const t=this.parents("form:first").find(":input[name=Version]").val(),n=t?o.default.sprintf(o.default._t("CMS.RollbackToVersion","Do you really want to roll back to version #%s of this page?"),t):o.default._t("CMS.ConfirmRestoreFromLive","Are you sure you want to revert draft to when the page was last published?");return confirm(n)?(this.parents("form:first").addClass("loading"),this._super(e)):(e.preventDefault(),!1)}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_archive:not(.homepage-warning)").entwine({onclick:function(e){var t;return t=this.parents("form:first").find("input[name=ArchiveWarningMessage]").val().replace(/\\n/g,"\n"),!!confirm(t)&&(this.parents("form:first").addClass("loading"),this._super(e))}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_restore").entwine({onclick:function(e){var t,n=this.parents("form:first").find(":input[name=Version]").val(),a=this.data("toRoot");return t=o.default.sprintf(o.default._t(a?"CMS.RestoreToRoot":"CMS.Restore"),n),!!confirm(t)&&(this.parents("form:first").addClass("loading"),this._super(e))}}),e(".cms-edit-form .btn-toolbar #Form_EditForm_action_unpublish:not(.homepage-warning)").entwine({onclick:function(e){var t,n=this.parents("form:first").find(":input[name=Version]").val();return t=o.default.sprintf(o.default._t("CMS.Unpublish"),n),!!confirm(t)&&(this.parents("form:first").addClass("loading"),this._super(e))}}),e(".cms-edit-form.changed").entwine({onmatch:function(t){this.find("button[data-text-alternate]").each((function(){const t=e(this),n=t.find(".btn__title"),a=t.data("textAlternate");a&&(t.data("textStandard",n.text()),n.text(a));const o=t.data("btnAlternate");o&&(t.data("btnStandard",t.attr("class")),t.attr("class",o),t.removeClass("btn-outline-secondary").addClass("btn-primary"));const i=t.data("btnAlternateAdd");i&&t.addClass(i);const r=t.data("btnAlternateRemove");r&&t.removeClass(r)})),this._super(t)},onunmatch:function(t){this.find("button[data-text-alternate]").each((function(){const t=e(this),n=t.find(".btn__title"),a=t.data("textStandard");a&&n.text(a);const o=t.data("btnStandard");o&&(t.attr("class",o),t.addClass("btn-outline-secondary").removeClass("btn-primary"));const i=t.data("btnAlternateAdd");i&&t.removeClass(i);const r=t.data("btnAlternateRemove");r&&t.addClass(r)})),this._super(t)}}),e(".cms-edit-form .btn-toolbar button[name=action_publish]").entwine({onbuttonafterrefreshalternate:function(){this.data("showingAlternate")?(this.addClass("btn-primary"),this.removeClass("btn-secondary")):(this.removeClass("btn-primary"),this.addClass("btn-secondary"))}}),e(".cms-edit-form .btn-toolbar button[name=action_save]").entwine({onbuttonafterrefreshalternate:function(){this.data("showingAlternate")?(this.addClass("btn-primary"),this.removeClass("btn-secondary")):(this.removeClass("btn-primary"),this.addClass("btn-secondary"))}}),e('.cms-edit-form.CMSPageSettingsController input[name="ParentType"]:checked').entwine({onmatch:function(){this.redraw(),this._super()},onunmatch:function(){this._super()},redraw:function(){var t=e(".cms-edit-form.CMSPageSettingsController #Form_EditForm_ParentID_Holder");"Form_EditForm_ParentType_root"==e(this).attr("id")?t.slideUp():t.slideDown()},onclick:function(){this.redraw()}}),"Form_EditForm_ParentType_root"==e('.cms-edit-form.CMSPageSettingsController input[name="ParentType"]:checked').attr("id")&&e(".cms-edit-form.CMSPageSettingsController #Form_EditForm_ParentID_Holder").hide();var t=!1;e(".cms-edit-form .btn-toolbar #Form_EditForm_action_unpublish.homepage-warning,.cms-edit-form .btn-toolbar #Form_EditForm_action_archive.homepage-warning,#Form_EditForm_URLSegment_Holder.homepage-warning .btn.update").entwine({onclick:async function(e){if(t)return this._super(e);e.stopPropagation();var n=o.default._t("CMS.RemoveHomePageWarningMessage","Warning: This page is the home page. By changing the URL segment visitors will not be able to view it.");return await(0,i.default)({title:o.default._t("CMS.RemoveHomePageWarningTitle","Remove your home page?"),message:n,confirmText:o.default._t("CMS.RemoveHomePageWarningLabel","Remove"),confirmColor:"danger"})&&(t=!0,this.trigger("click"),t=!1),!1}})}))},55:function(e,t,n){var a=s(n(669)),o=s(n(815)),i=s(n(216)),r=n(125);function s(e){return e&&e.__esModule?e:{default:e}}a.default.entwine("ss.tree",(function(e){e(".cms-tree").entwine({fromDocument:{"oncontext_show.vakata":function(e){this.adjustContextClass()}},adjustContextClass:function(){var t=e("#vakata-contextmenu").find("ul ul");t.each((function(n){var a="1",o=e(t[n]).find("li").length;o>20?a="3":o>10&&(a="2"),e(t[n]).addClass("vakata-col-"+a).removeClass("right"),e(t[n]).find("li").on("mouseenter",(function(t){e(this).parent("ul").removeClass("right")}))}))},showListViewFor:function(t){localStorage.setItem("ss.pages-view-type","listview");const n=this.closest(".cms-content-view").data("url-listviewroot"),a=e.path.addSearchParams(n,{ParentID:t}),o=e("base").attr("href")||"";window.location.assign((0,r.joinUrlPaths)(o,a))},getTreeConfig:function(){var t=this,n=this._super();this.getHints();return n.plugins.push("contextmenu"),n.contextmenu={items:function(n){var a={edit:{label:n.hasClass("edit-disabled")?o.default._t("CMS.EditPage","Edit page",100,"Used in the context menu when right-clicking on a page node in the CMS tree"):o.default._t("CMS.ViewPage","View page",100,"Used in the context menu when right-clicking on a page node in the CMS tree"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(o.default.sprintf(t.data("urlEditpage"),n.data("id")))}}};n.hasClass("nochildren")||(a.showaslist={label:o.default._t("CMS.ShowAsList"),action:function(e){t.showListViewFor(e.data("id"))}});n.data("pagetype");var i=n.data("id"),r=n.find(">a .item").data("allowedchildren"),s={},l=!1;return e.each(r,(function(n,a){l=!0,s["allowedchildren-"+a.ClassName]={label:'<span class="jstree-recordicon '+a.IconClass+'"></span>'+a.Title,_class:"class-"+a.ClassName.replace(/[^a-zA-Z0-9\-_:.]+/g,"_"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(e.path.addSearchParams(o.default.sprintf(t.data("urlAddpage"),i,a.ClassName),t.data("extraParams")))}}})),l&&(a.addsubpage={label:o.default._t("CMS.AddSubPage","Add page under this page",100,"Used in the context menu when right-clicking on a page node in the CMS tree"),submenu:s}),n.hasClass("edit-disabled")||(a.duplicate={label:o.default._t("CMS.Duplicate"),submenu:[{label:o.default._t("CMS.ThisPageOnly"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(e.path.addSearchParams(o.default.sprintf(t.data("urlDuplicate"),n.data("id")),t.data("extraParams")))}},{label:o.default._t("CMS.ThisPageAndSubpages"),action:function(n){e(".cms-container").entwine(".ss").loadPanel(e.path.addSearchParams(o.default.sprintf(t.data("urlDuplicatewithchildren"),n.data("id")),t.data("extraParams")))}}]}),a}},n},canMove:async function(e){if(!(e.rslt.o.find(".homepage").first().length>0))return!0;if(e.rslt.op.data("id")===e.rslt.np.data("id"))return!0;var t=o.default._t("CMS.RemoveHomePageWarningMessage","Warning: This page is the home page. By changing the URL segment visitors will not be able to view it.");return await(0,i.default)({title:o.default._t("CMS.RemoveHomePageWarningTitle","Remove your home page?"),message:t,confirmText:o.default._t("CMS.RemoveHomePageWarningLabel","Remove"),confirmColor:"danger"})}}),e(".cms-tree a.jstree-clicked").entwine({onmatch:function(){var e=this,t=e.parents(".cms-tree-view-sidebar");if(e.offset().top<0||e.offset().top>t.height()-e.height()){var n=e.parent();n.prev().length&&(n=n.prev()),n.get(0).scrollIntoView()}}}),e(".cms-tree-filtered .clear-filter").entwine({onclick:function(){window.location=location.protocol+"//"+location.host+location.pathname}}),e(".cms-tree .subtree-list-link").entwine({onclick:function(e){e.preventDefault(),this.closest(".cms-tree").showListViewFor(this.data("id"))}})}))},881:function(e,t,n){var a,o=(a=n(669))&&a.__esModule?a:{default:a},i=n(125);o.default.entwine("ss",(function(e){const t="treeview",n="listview";e(".cms-content-header-info").entwine({"from .cms-panel":{ontoggle:function(e){var t=this.closest(".cms-content").find(e.target);0!==t.length&&this.parent()[t.hasClass("collapsed")?"addClass":"removeClass"]("collapsed")}}}),e(".cms-panel-deferred.cms-content-view").entwine({onadd:function(){if(this.data("no-ajax"))return;var e=localStorage.getItem("ss.pages-view-type")||t;this.closest(".cms-content-tools").length>0&&(e=t);const a=this.data(`url-${e}`);let o=localStorage.getItem("ss.pages-view-filtered");"string"==typeof o&&"false"===o.toLowerCase()&&(o=!1),localStorage.setItem("ss.pages-view-filtered",!1),this.data("deferredNoCache",o||e===n),this.data("url",a+location.search),this._super()}}),e(".js-injector-boot .search-holder--cms").entwine({search(e){localStorage.setItem("ss.pages-view-filtered",!0),this._super(e)}}),e(".cms .page-view-link").entwine({onclick:function(t){t.preventDefault();const a=e(this).data("view"),o=this.closest(".cms-content-view"),r=o.data(`url-${a}`),s=0!==o.closest(".cms-content-tools").length;if(localStorage.setItem("ss.pages-view-type",a),s&&a===n){const t=e("base").attr("href")||"";window.location.assign((0,i.joinUrlPaths)(t,o.data("url-listviewroot")))}else o.data("url",r+location.search),o.redraw()}}),e(".cms .cms-clear-filter").entwine({onclick:function(t){t.preventDefault(),window.location=e(this).prop("href")}}),e(".cms-content-toolbar").entwine({onmatch:function(){var t=this;this._super(),e.each(this.find(".cms-actions-buttons-row .tool-button"),(function(){var n=e(this),a=n.data("toolid");n.hasClass("active");void 0!==a&&(n.data("active",!1).removeClass("active"),e("#"+a).hide(),t.bindActionButtonEvents(n))}))},onunmatch:function(){var t=this;this._super(),e.each(this.find(".cms-actions-buttons-row .tool-button"),(function(){var n=e(this);t.unbindActionButtonEvents(n)}))},bindActionButtonEvents:function(e){var t=this;e.on("click.cmsContentToolbar",(function(n){t.showHideTool(e)}))},unbindActionButtonEvents:function(e){e.off(".cmsContentToolbar")},showHideTool:function(t){var n=t.data("active"),a=t.data("toolid"),o=e("#"+a);e.each(this.find(".cms-actions-buttons-row .tool-button"),(function(){var t=e(this),n=e("#"+t.data("toolid"));t.data("toolid")!==a&&(n.hide(),t.data("active",!1))})),t[n?"removeClass":"addClass"]("active"),o[n?"hide":"show"](),t.data("active",!n)}})}))},739:function(e,t,n){var a;((a=n(669))&&a.__esModule?a:{default:a}).default.entwine("ss",(function(e){e("#Form_EditForm_RedirectionType input").entwine({onmatch:function(){e(this).attr("checked")&&this.toggle(),this._super()},onunmatch:function(){this._super()},onclick:function(){this.toggle()},toggle:function(){"Internal"==e(this).attr("value")?(e("#Form_EditForm_ExternalURL_Holder").hide(),e("#Form_EditForm_LinkToID_Holder").show(),e("#Form_EditForm_LinkToFile_Holder").hide()):"External"==e(this).attr("value")?(e("#Form_EditForm_ExternalURL_Holder").show(),e("#Form_EditForm_LinkToID_Holder").hide(),e("#Form_EditForm_LinkToFile_Holder").hide()):(e("#Form_EditForm_LinkToFile_Holder").show(),e("#Form_EditForm_ExternalURL_Holder").hide(),e("#Form_EditForm_LinkToID_Holder").hide())}})}))},978:function(e,t,n){var a;((a=n(669))&&a.__esModule?a:{default:a}).default.entwine("ss",(function(e){e(".field.urlsegment:not(.readonly)").entwine({MaxPreviewLength:55,Ellipsis:"...",onmatch:function(){this.find(":text").length&&this.toggleEdit(!1),this.redraw(),this._super()},redraw:function(){var e=this.find(":text"),t=decodeURI(e.data("prefix")+e.val()),n=t;t.length>this.getMaxPreviewLength()&&(n=this.getEllipsis()+t.substr(t.length-this.getMaxPreviewLength(),t.length)),this.find(".URL-link").attr("href",encodeURI(t+e.data("suffix"))).text(n)},toggleEdit:function(e){var t=this.find(":text");this.find(".preview-holder")[e?"hide":"show"](),this.find(".edit-holder")[e?"show":"hide"](),e&&(t.data("origval",t.val()),t.focus())},update:function(){var e=this,t=this.find(":text"),n=t.data("origval"),a=arguments[0],o=a&&""!==a?a:t.val();n!=o?(this.addClass("loading"),this.suggest(o,(function(n){t.val(decodeURIComponent(n.value)),e.toggleEdit(!1),e.removeClass("loading"),e.redraw()}))):(this.toggleEdit(!1),this.redraw())},cancel:function(){var e=this.find(":text");e.val(e.data("origval")),this.toggleEdit(!1)},suggest:function(t,n){var a=this,o=a.find(":text"),i=e.path.parseUrl(a.closest("form").attr("action")),r=i.hrefNoSearch+"/field/"+o.attr("name")+"/suggest/?value="+encodeURIComponent(t);i.search&&(r+="&"+i.search.replace(/^\?/,"")),e.ajax({url:r,success:function(e){n.apply(this,arguments)},error:function(e,t){e.statusText=e.responseText},complete:function(){a.removeClass("loading")}})}}),e(".field.urlsegment .text").entwine({onkeydown:function(e){13===e.keyCode&&(e.preventDefault(),this.closest(".field").update())}}),e(".field.urlsegment .edit").entwine({onclick:function(e){e.preventDefault(),this.closest(".field").toggleEdit(!0)}}),e(".field.urlsegment .update").entwine({onclick:function(e){e.preventDefault(),this.closest(".field").update()}}),e(".field.urlsegment .cancel").entwine({onclick:function(e){e.preventDefault(),this.closest(".field").cancel()}})}))},803:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;t.default={ANCHORSELECTOR_CURRENT_FIELD:"ANCHORSELECTOR_CURRENT_FIELD",ANCHORSELECTOR_UPDATED:"ANCHORSELECTOR_UPDATED",ANCHORSELECTOR_UPDATING:"ANCHORSELECTOR_UPDATING",ANCHORSELECTOR_UPDATE_FAILED:"ANCHORSELECTOR_UPDATE_FAILED"}},979:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.beginUpdating=function(e){return{type:o.default.ANCHORSELECTOR_UPDATING,payload:{pageId:e}}},t.updateFailed=function(e){return{type:o.default.ANCHORSELECTOR_UPDATE_FAILED,payload:{pageId:e}}},t.updated=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return{type:o.default.ANCHORSELECTOR_UPDATED,payload:{pageId:e,anchors:t,cacheResult:n}}},t.updatedCurrentField=function(e,t,n){return{type:o.default.ANCHORSELECTOR_CURRENT_FIELD,payload:{pageId:e,anchors:t,fieldID:n}}};var a,o=(a=n(803))&&a.__esModule?a:{default:a}},796:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:s,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;const n=(n,o)=>{const i=t.payload.pageId;return(0,a.default)({pages:[...e.pages.filter((e=>e.id!==i)),{id:i,loadingState:n,anchors:o}].sort(((e,t)=>e.id-t.id))})};switch(t.type){case o.default.ANCHORSELECTOR_UPDATING:return n(i.default.UPDATING,[]);case o.default.ANCHORSELECTOR_UPDATED:{const{anchors:e,cacheResult:a}=t.payload,{SUCCESS:o,DIRTY:r}=i.default;return n(a?o:r,e)}case o.default.ANCHORSELECTOR_CURRENT_FIELD:{const{anchors:e}=t.payload;return n(i.default.FIELD_ONLY,e)}case o.default.ANCHORSELECTOR_UPDATE_FAILED:return n(i.default.FAILED,[]);default:return e}};var a=r(n(923)),o=r(n(803)),i=r(n(996));function r(e){return e&&e.__esModule?e:{default:e}}const s=(0,a.default)({pages:[]})},996:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;t.default={SUCCESS:"SUCCESS",DIRTY:"DIRTY",FIELD_ONLY:"FIELD_ONLY",UPDATING:"UPDATING",FAILED:"FAILED"}},560:function(e,t,n){function a(e){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},a(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o,i=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!==a(e)&&"function"!=typeof e)return{default:e};var n=l(t);if(n&&n.has(e))return n.get(e);var o={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var r in e)if("default"!==r&&Object.prototype.hasOwnProperty.call(e,r)){var s=i?Object.getOwnPropertyDescriptor(e,r):null;s&&(s.get||s.set)?Object.defineProperty(o,r,s):o[r]=e[r]}o.default=e,n&&n.set(e,o);return o}(n(594)),r=(o=n(935))&&o.__esModule?o:{default:o},s=n(556);function l(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(l=function(e){return e?n:t})(e)}function d(){return d=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(e[a]=n[a])}return e},d.apply(this,arguments)}var c=function(e){var t=e.onClose,n=e.message,a=e.title,o=e.confirmText,r=e.cancelText,l=e.confirmColor,c=e.cancelColor,u=e.className,f=e.buttonsComponent,h=e.size,p=e.bodyComponent,m=e.modalProps,g=i.default.createElement(i.Fragment,null,r&&i.default.createElement(s.Button,{color:c,onClick:function(){return t(!1)}},r)," ",i.default.createElement(s.Button,{color:l,onClick:function(){return t(!0)}},o));if(f){var v=f;g=i.default.createElement(v,{onClose:t})}var _=p;return i.default.createElement(s.Modal,d({size:h,isOpen:!0,toggle:function(){return t(!1)},className:"reactstrap-confirm ".concat(u)},m),a&&i.default.createElement(s.ModalHeader,{toggle:function(){return t(!1)}},a||null),i.default.createElement(s.ModalBody,null,p?i.default.createElement(_,null):n),i.default.createElement(s.ModalFooter,null,g))};c.defaultProps={message:"Are you sure?",title:"Warning!",confirmText:"Ok",cancelText:"Cancel",confirmColor:"primary",cancelColor:"",className:"",buttonsComponent:null,size:null,bodyComponent:null,modalProps:{}},c.propTypes={onClose:r.default.func.isRequired,message:r.default.node,title:r.default.node,confirmText:r.default.node,cancelText:r.default.node,confirmColor:r.default.string,cancelColor:r.default.string,className:r.default.string,size:r.default.string,buttonsComponent:r.default.func,bodyComponent:r.default.func,modalProps:r.default.object};var u=c;t.default=u},216:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=r(n(594)),o=n(518),i=r(n(560));function r(e){return e&&e.__esModule?e:{default:e}}function s(){return s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(e[a]=n[a])}return e},s.apply(this,arguments)}var l=function(e){return new Promise((function(t){var n=document.createElement("div");(0,o.render)(a.default.createElement(i.default,s({},e,{onClose:function(e){(0,o.unmountComponentAtNode)(n),n=null,t(e)}})),n)}))};t.default=l},923:function(e){e.exports=DeepFreezeStrict},657:function(e){e.exports=EmotionCssCacheProvider},623:function(e){e.exports=FieldHolder},207:function(e){e.exports=Injector},950:function(e){e.exports=IsomorphicFetch},935:function(e){e.exports=PropTypes},594:function(e){e.exports=React},518:function(e){e.exports=ReactDom},40:function(e){e.exports=ReactRedux},315:function(e){e.exports=ReactSelectCreatable},556:function(e){e.exports=Reactstrap},367:function(e){e.exports=Redux},381:function(e){e.exports=ReduxForm},898:function(e){e.exports=SilverStripeComponent},304:function(e){e.exports=classnames},432:function(e){e.exports=getFormState},815:function(e){e.exports=i18n},669:function(e){e.exports=jQuery},125:function(e){e.exports=ssUrlLib}},t={};function n(a){var o=t[a];if(void 0!==o)return o.exports;var i=t[a]={exports:{}};return e[a](i,i.exports,n),i.exports}n(586),n(677),n(881),n(55),n(739),n(978),n(38)}();
|
||||
|
@ -123,16 +123,16 @@ $.entwine('ss', function($){
|
||||
* @param {string} defaultChildClass
|
||||
*/
|
||||
updateSelectionFilter: function(disallowedChildren, defaultChildClass) {
|
||||
var currentSelection = this.find('#Form_AddForm_PageType div.radio.selected')[0];
|
||||
var currentSelection = this.find('#Form_AddForm_RecordType div.radio.selected')[0];
|
||||
var keepSelection = false;
|
||||
|
||||
// Limit selection
|
||||
var allAllowed = null; // troolian
|
||||
this.find('#Form_AddForm_PageType div.radio').each(function (i, el) {
|
||||
this.find('#Form_AddForm_RecordType div.radio').each(function (i, el) {
|
||||
var className = $(this).find('input').val(),
|
||||
isAllowed = ($.inArray(className, disallowedChildren) === -1);
|
||||
|
||||
// Avoid changing the selected pagetype if still allowed
|
||||
// Avoid changing the selected record type if still allowed
|
||||
if (el === currentSelection && isAllowed) {
|
||||
keepSelection = true;
|
||||
}
|
||||
@ -153,16 +153,16 @@ $.entwine('ss', function($){
|
||||
var selectedEl = $(currentSelection).parents('li:first');
|
||||
} else if (defaultChildClass) {
|
||||
var selectedEl = this
|
||||
.find('#Form_AddForm_PageType div.radio input[value=' + defaultChildClass + ']')
|
||||
.find('#Form_AddForm_RecordType div.radio input[value=' + defaultChildClass + ']')
|
||||
.parents('li:first');
|
||||
} else {
|
||||
var selectedEl = this.find('#Form_AddForm_PageType div.radio:not(.disabled):first');
|
||||
var selectedEl = this.find('#Form_AddForm_RecordType div.radio:not(.disabled):first');
|
||||
}
|
||||
selectedEl.setSelected(true);
|
||||
selectedEl.siblings().setSelected(false);
|
||||
|
||||
// Disable the "Create" button if none of the pagetypes are available
|
||||
if(this.find('#Form_AddForm_PageType div.radio:not(.disabled)').length) {
|
||||
// Disable the "Create" button if none of the record types are available
|
||||
if(this.find('#Form_AddForm_RecordType div.radio:not(.disabled)').length) {
|
||||
this.find('button[name=action_doAdd]').removeAttr('disabled');
|
||||
} else {
|
||||
this.find('button[name=action_doAdd]').attr('disabled', 'disabled');
|
||||
@ -172,7 +172,7 @@ $.entwine('ss', function($){
|
||||
}
|
||||
});
|
||||
|
||||
$(".cms-add-form #Form_AddForm_PageType div.radio").entwine({
|
||||
$(".cms-add-form #Form_AddForm_RecordType div.radio").entwine({
|
||||
onclick: function(e) {
|
||||
this.setSelected(true);
|
||||
},
|
||||
|
@ -91,7 +91,7 @@ $.entwine('ss.tree', function($) {
|
||||
$.each(allowedChildren, function(index, child) {
|
||||
hasAllowedChildren = true;
|
||||
menuAllowedChildren["allowedchildren-" + child.ClassName] = {
|
||||
'label': '<span class="jstree-pageicon ' + child.IconClass + '"></span>' + child.Title,
|
||||
'label': '<span class="jstree-recordicon ' + child.IconClass + '"></span>' + child.Title,
|
||||
'_class': 'class-' + child.ClassName.replace(/[^a-zA-Z0-9\-_:.]+/g, '_'),
|
||||
'action': function(obj) {
|
||||
$('.cms-container').entwine('.ss').loadPanel(
|
||||
|
@ -13,10 +13,11 @@ use SilverStripe\CMS\BatchActions\CMSBatchAction_Archive;
|
||||
use SilverStripe\CMS\BatchActions\CMSBatchAction_Publish;
|
||||
use SilverStripe\CMS\BatchActions\CMSBatchAction_Unpublish;
|
||||
use SilverStripe\CMS\Controllers\CMSSiteTreeFilter_Search;
|
||||
use SilverStripe\CMS\Model\CurrentPageIdentifier;
|
||||
use SilverStripe\CMS\Model\RedirectorPage;
|
||||
use SilverStripe\CMS\Forms\CMSMainAddForm;
|
||||
use SilverStripe\CMS\Model\CurrentRecordIdentifier;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\CMS\Model\VirtualPage;
|
||||
use SilverStripe\CMS\Search\SearchForm;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
@ -24,11 +25,14 @@ use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Control\PjaxResponseNegotiator;
|
||||
use SilverStripe\Core\Cache\MemberCacheFlusher;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Core\Flushable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ModuleResource;
|
||||
use SilverStripe\Core\Manifest\ModuleResourceLoader;
|
||||
use SilverStripe\Forms\DateField;
|
||||
use SilverStripe\Forms\DropdownField;
|
||||
use SilverStripe\Forms\FieldGroup;
|
||||
@ -47,10 +51,8 @@ use SilverStripe\Forms\LabelField;
|
||||
use SilverStripe\Forms\LiteralField;
|
||||
use SilverStripe\Forms\Tab;
|
||||
use SilverStripe\Forms\TabSet;
|
||||
use SilverStripe\Forms\TextField;
|
||||
use SilverStripe\Model\List\ArrayList;
|
||||
use SilverStripe\ORM\CMSPreviewable;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||
@ -60,7 +62,6 @@ use SilverStripe\ORM\Hierarchy\MarkedSet;
|
||||
use SilverStripe\Model\List\SS_List;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Security\InheritedPermissions;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\PermissionProvider;
|
||||
use SilverStripe\Security\Security;
|
||||
@ -71,60 +72,56 @@ use SilverStripe\Versioned\ChangeSetItem;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\VersionedAdmin\Controllers\CMSPageHistoryViewerController;
|
||||
use SilverStripe\Model\ArrayData;
|
||||
use SilverStripe\Versioned\RecursivePublishable;
|
||||
use SilverStripe\View\Requirements;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
|
||||
/**
|
||||
* The main "content" area of the CMS.
|
||||
*
|
||||
* This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
|
||||
* admin menu.
|
||||
*
|
||||
* @mixin LeftAndMainPageIconsExtension
|
||||
*/
|
||||
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider, Flushable, MemberCacheFlusher
|
||||
class CMSMain extends LeftAndMain implements CurrentRecordIdentifier, PermissionProvider, Flushable, MemberCacheFlusher
|
||||
{
|
||||
/**
|
||||
* Unique ID for page icons CSS block
|
||||
*/
|
||||
const PAGE_ICONS_ID = 'PageIcons';
|
||||
public const PAGE_ICONS_ID = 'PageIcons'; // @TODO AHHHH!!!
|
||||
|
||||
private static $url_segment = 'pages';
|
||||
private static string $url_segment = 'pages';
|
||||
|
||||
private static $url_rule = '/$Action/$ID/$OtherID';
|
||||
private static string $url_rule = '/$Action/$ID/$OtherID';
|
||||
|
||||
// Maintain a lower priority than other administration sections
|
||||
// so that Director does not think they are actions of CMSMain
|
||||
private static $url_priority = 39;
|
||||
private static int $url_priority = 39;
|
||||
|
||||
private static $menu_title = 'Edit Page';
|
||||
private static $menu_title = 'Pages';
|
||||
|
||||
private static $menu_icon_class = 'font-icon-sitemap';
|
||||
private static string $menu_icon_class = 'font-icon-sitemap';
|
||||
|
||||
private static $menu_priority = 10;
|
||||
private static int $menu_priority = 10;
|
||||
|
||||
private static $tree_class = SiteTree::class;
|
||||
private static string $tree_class = SiteTree::class;
|
||||
|
||||
private static $session_namespace = CMSMain::class;
|
||||
private static string $session_namespace = CMSMain::class;
|
||||
|
||||
private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
|
||||
private static string|array $required_permission_codes = 'CMS_ACCESS_CMSMain';
|
||||
|
||||
/**
|
||||
* Should the archive warning message be dynamic based on the specific content? This is slow on larger sites and can be disabled.
|
||||
*
|
||||
* @config
|
||||
* @var bool
|
||||
*/
|
||||
private static $enable_dynamic_archive_warning_message = true;
|
||||
private static bool $enable_dynamic_archive_warning_message = true;
|
||||
|
||||
/**
|
||||
* Amount of results showing on a single page.
|
||||
*
|
||||
* @config
|
||||
* @var int
|
||||
*/
|
||||
private static $page_length = 15;
|
||||
private static int $page_length = 15;
|
||||
|
||||
private static $allowed_actions = [
|
||||
private static array $allowed_actions = [
|
||||
'add',
|
||||
'AddForm',
|
||||
'archive',
|
||||
'deleteitems',
|
||||
'DeleteItemsForm',
|
||||
@ -138,7 +135,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
'EditForm',
|
||||
'schema',
|
||||
'SearchForm',
|
||||
'SiteTreeAsUL',
|
||||
'TreeAsUL',
|
||||
'getshowdeletedsubtree',
|
||||
'savetreenode',
|
||||
'getsubtree',
|
||||
@ -150,32 +147,32 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
'childfilter',
|
||||
];
|
||||
|
||||
private static $url_handlers = [
|
||||
private static array $url_handlers = [
|
||||
'EditForm/$ID' => 'EditForm',
|
||||
];
|
||||
|
||||
private static $casting = [
|
||||
private static array $casting = [
|
||||
'TreeIsFiltered' => 'Boolean',
|
||||
'AddForm' => 'HTMLFragment',
|
||||
'LinkPages' => 'Text',
|
||||
'LinkRecords' => 'Text',
|
||||
'Link' => 'Text',
|
||||
'ListViewForm' => 'HTMLFragment',
|
||||
'ExtraTreeTools' => 'HTMLFragment',
|
||||
'PageList' => 'HTMLFragment',
|
||||
'PageListSidebar' => 'HTMLFragment',
|
||||
'SiteTreeHints' => 'HTMLFragment',
|
||||
'RecordList' => 'HTMLFragment',
|
||||
'TreeHints' => 'HTMLFragment',
|
||||
'SecurityID' => 'Text',
|
||||
'SiteTreeAsUL' => 'HTMLFragment',
|
||||
'TreeAsUL' => 'HTMLFragment',
|
||||
'RecordTreeMarkup' => 'HTMLFragment',
|
||||
];
|
||||
|
||||
private static $dependencies = [
|
||||
'HintsCache' => '%$' . CacheInterface::class . '.CMSMain_SiteTreeHints',
|
||||
private static array $dependencies = [
|
||||
'HintsCache' => '%$' . CacheInterface::class . '.CMSMain_TreeHints',
|
||||
'creatableChildrenCache' => '%$' . CacheInterface::class . '.SiteTree_CreatableChildren',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var CacheInterface
|
||||
*/
|
||||
protected $hintsCache;
|
||||
protected ?CacheInterface $hintsCache = null;
|
||||
|
||||
private ?CacheInterface $creatableChildrenCache = null;
|
||||
|
||||
protected function init()
|
||||
{
|
||||
@ -197,7 +194,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
// In case we're not showing a specific record, explicitly remove any session state,
|
||||
// to avoid it being highlighted in the tree, and causing an edit form to show.
|
||||
if (!$request->param('Action')) {
|
||||
$this->setCurrentPageID(null);
|
||||
$this->setCurrentRecordID(null);
|
||||
}
|
||||
|
||||
return parent::index($request);
|
||||
@ -216,51 +213,34 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages listing area
|
||||
*
|
||||
* @return DBHTMLText
|
||||
* Get record listing area
|
||||
*/
|
||||
public function PageList()
|
||||
public function RecordList(): DBHTMLText
|
||||
{
|
||||
return $this->renderWith($this->getTemplatesWithSuffix('_PageList'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Page list view for edit-form
|
||||
*
|
||||
* @return DBHTMLText
|
||||
*/
|
||||
public function PageListSidebar()
|
||||
{
|
||||
return $this->renderWith($this->getTemplatesWithSuffix('_PageList_Sidebar'));
|
||||
return $this->renderWith($this->getTemplatesWithSuffix('_RecordList'));
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is set to true, the "switchView" context in the
|
||||
* template is shown, with links to the staging and publish site.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function ShowSwitchView()
|
||||
public function ShowSwitchView(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
|
||||
* Overloads the LeftAndMain::ShowView. Allows to pass a record as a parameter, so we are able
|
||||
* to switch view also for archived versions.
|
||||
*
|
||||
* @param SiteTree $page
|
||||
* @return array
|
||||
*/
|
||||
public function SwitchView($page = null)
|
||||
public function SwitchView(?DataObject $record = null): array
|
||||
{
|
||||
if (!$page) {
|
||||
$page = $this->currentPage();
|
||||
if (!$record) {
|
||||
$record = $this->currentRecord();
|
||||
}
|
||||
|
||||
if ($page) {
|
||||
$nav = SilverStripeNavigator::get_for_record($page);
|
||||
if ($record) {
|
||||
$nav = SilverStripeNavigator::get_for_record($record);
|
||||
return $nav['items'];
|
||||
}
|
||||
}
|
||||
@ -289,14 +269,19 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return $link;
|
||||
}
|
||||
|
||||
public function LinkPages()
|
||||
public function LinkRecords()
|
||||
{
|
||||
return CMSPagesController::singleton()->Link();
|
||||
// @TODO remove this and replace with just Link() when other CMSMain subclasses are gone
|
||||
$controller = $this;
|
||||
if (static::class !== CMSMain::class) {
|
||||
$controller = CMSMain::singleton();
|
||||
}
|
||||
return $controller->Link();
|
||||
}
|
||||
|
||||
public function LinkPagesWithSearch()
|
||||
public function LinkRecordsWithSearch()
|
||||
{
|
||||
return $this->LinkWithSearch($this->LinkPages());
|
||||
return $this->LinkWithSearch($this->LinkRecords());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -306,7 +291,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
*/
|
||||
public function LinkTreeView()
|
||||
{
|
||||
// Tree view is just default link to main pages section (no /treeview suffix)
|
||||
// Tree view is just default link to main section (no /treeview suffix)
|
||||
return CMSMain::singleton()->Link();
|
||||
}
|
||||
|
||||
@ -317,12 +302,12 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
*/
|
||||
public function LinkListView()
|
||||
{
|
||||
// Note : Force redirect to top level page controller (no parentid)
|
||||
// Note : Force redirect to top level record controller (no parentid)
|
||||
return $this->LinkWithSearch(CMSMain::singleton()->Link('listview'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Link to list view for children of a parent page
|
||||
* Link to list view for children of a parent record
|
||||
*
|
||||
* @param int|string $parentID Literal parentID, or placeholder (e.g. '%d') for
|
||||
* client side substitution
|
||||
@ -366,28 +351,31 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link for editing a page.
|
||||
* Get the link for editing a record.
|
||||
*
|
||||
* @see CMSEditLinkExtension::getCMSEditLinkForManagedDataObject()
|
||||
*/
|
||||
public function getCMSEditLinkForManagedDataObject(SiteTree $obj): string
|
||||
public function getCMSEditLinkForManagedDataObject(DataObject $obj): string
|
||||
{
|
||||
return Controller::join_links(CMSPageEditController::singleton()->Link('show'), $obj->ID);
|
||||
}
|
||||
|
||||
public function LinkPageEdit($id = null)
|
||||
public function LinkRecordEdit($id = null)
|
||||
{
|
||||
if (!$id) {
|
||||
$id = $this->currentPageID();
|
||||
$id = $this->currentRecordID();
|
||||
}
|
||||
return $this->LinkWithSearch(
|
||||
Controller::join_links(CMSPageEditController::singleton()->Link('show'), $id)
|
||||
);
|
||||
}
|
||||
|
||||
public function LinkPageSettings()
|
||||
public function LinkRecordSettings()
|
||||
{
|
||||
if ($id = $this->currentPageID()) {
|
||||
if (!DataObject::singleton($this->getModelClass())->hasMethod('getSettingsFields')) { // @TODO This is awful, I'd much rather it just be part of the main form.
|
||||
return null;
|
||||
}
|
||||
if ($id = $this->currentRecordID()) {
|
||||
return $this->LinkWithSearch(
|
||||
Controller::join_links(CMSPageSettingsController::singleton()->Link('show'), $id)
|
||||
);
|
||||
@ -396,10 +384,10 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
}
|
||||
|
||||
public function LinkPageHistory()
|
||||
public function LinkRecordHistory()
|
||||
{
|
||||
$controller = Injector::inst()->get(CMSPageHistoryViewerController::class);
|
||||
if (($id = $this->currentPageID()) && $controller) {
|
||||
if (($id = $this->currentRecordID()) && $controller) {
|
||||
if ($controller) {
|
||||
return $this->LinkWithSearch(
|
||||
Controller::join_links($controller->Link('show'), $id)
|
||||
@ -421,31 +409,35 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return 'edit';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CacheInterface $cache
|
||||
* @return $this
|
||||
*/
|
||||
public function setHintsCache(CacheInterface $cache)
|
||||
public function setHintsCache(CacheInterface $cache): static
|
||||
{
|
||||
$this->hintsCache = $cache;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CacheInterface $cache
|
||||
*/
|
||||
public function getHintsCache()
|
||||
public function getHintsCache(): ?CacheInterface
|
||||
{
|
||||
return $this->hintsCache;
|
||||
}
|
||||
|
||||
public function setCreatableChildrenCache(CacheInterface $cache): static
|
||||
{
|
||||
$this->creatableChildrenCache = $cache;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatableChildrenCache(): ?CacheInterface
|
||||
{
|
||||
return $this->creatableChildrenCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all dependent cache backends
|
||||
*/
|
||||
public function clearCache()
|
||||
{
|
||||
$this->getHintsCache()->clear();
|
||||
$this->getCreatableChildrenCache()->clear();
|
||||
}
|
||||
|
||||
public function LinkWithSearch($link)
|
||||
@ -463,10 +455,10 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return $link;
|
||||
}
|
||||
|
||||
public function LinkPageAdd($extra = null, $placeholders = null)
|
||||
public function LinkRecordAdd($extra = null, $placeholders = null)
|
||||
{
|
||||
$link = CMSPageAddController::singleton()->Link();
|
||||
$this->extend('updateLinkPageAdd', $link);
|
||||
$link = $this->Link('add');
|
||||
$this->extend('updateLinkRecordAdd', $link);
|
||||
|
||||
if ($extra) {
|
||||
$link = Controller::join_links($link, $extra);
|
||||
@ -479,30 +471,27 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function LinkPreview()
|
||||
public function add()
|
||||
{
|
||||
$record = $this->getRecord($this->currentPageID());
|
||||
$baseLink = Director::absoluteBaseURL();
|
||||
if ($record && $record instanceof SiteTree) {
|
||||
// if we are an external redirector don't show a link
|
||||
if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
|
||||
$baseLink = false;
|
||||
} else {
|
||||
$baseLink = $record->Link('?stage=Stage');
|
||||
if ($this->getRequest()->isAjax()) {
|
||||
return $this->AddForm()->forTemplate();
|
||||
}
|
||||
return $this->render([
|
||||
'Content' => DBHTMLText::create()->setValue($this->AddForm()->forTemplate()),
|
||||
]);
|
||||
}
|
||||
return $baseLink;
|
||||
|
||||
public function AddForm(): Form
|
||||
{
|
||||
return CMSMainAddForm::create($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entire site tree as a nested set of ULs
|
||||
*/
|
||||
public function SiteTreeAsUL()
|
||||
public function TreeAsUL()
|
||||
{
|
||||
$treeClass = $this->config()->get('tree_class');
|
||||
$treeClass = $this->getModelClass();
|
||||
$filter = $this->getSearchFilter();
|
||||
|
||||
DataObject::singleton($treeClass)->prepopulateTreeDataCache(null, [
|
||||
@ -510,9 +499,9 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
'numChildrenMethod' => $filter ? $filter->getNumChildrenMethod() : 'numChildren',
|
||||
]);
|
||||
|
||||
$html = $this->getSiteTreeFor($treeClass);
|
||||
$html = $this->getTreeFor($treeClass);
|
||||
|
||||
$this->extend('updateSiteTreeAsUL', $html);
|
||||
$this->extend('updateTreeAsUL', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
@ -528,9 +517,9 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
* @param string $numChildrenMethod
|
||||
* @param callable $filterFunction
|
||||
* @param int $nodeCountThreshold
|
||||
* @return string Nested unordered list with links to each page
|
||||
* @return string Nested unordered list with links to each record
|
||||
*/
|
||||
public function getSiteTreeFor(
|
||||
public function getTreeFor(
|
||||
$className,
|
||||
$rootID = null,
|
||||
$childrenMethod = null,
|
||||
@ -550,7 +539,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
if (!$filterFunction) {
|
||||
$filterFunction = function ($node) use ($filter) {
|
||||
return $filter->isPageIncluded($node);
|
||||
return $filter->isRecordIncluded($node);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -568,20 +557,22 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
// Mark tree from this node
|
||||
$markingSet->markPartialTree();
|
||||
|
||||
// Ensure current page is exposed
|
||||
$currentPage = $this->currentPage();
|
||||
if ($currentPage) {
|
||||
$markingSet->markToExpose($currentPage);
|
||||
// Ensure current record is exposed
|
||||
$currentRecord = $this->currentRecord();
|
||||
if ($currentRecord) {
|
||||
$markingSet->markToExpose($currentRecord);
|
||||
}
|
||||
|
||||
// Pre-cache permissions
|
||||
$checker = SiteTree::getPermissionChecker();
|
||||
$modelClass = $this->getModelClass();
|
||||
$checker = ClassInfo::hasMethod($modelClass, 'getPermissionChecker') ? $modelClass::getPermissionChecker() : null; // @TODO eww why is it static?
|
||||
if ($checker instanceof InheritedPermissions) {
|
||||
$checker->prePopulatePermissionCache(
|
||||
InheritedPermissions::EDIT,
|
||||
$markingSet->markedNodeIDs()
|
||||
);
|
||||
}
|
||||
// @TODO if we don't have inherited permissions, make sure we still DO do permission checks where needed!!
|
||||
|
||||
// Render using full-subtree template
|
||||
return $markingSet->renderChildren(
|
||||
@ -590,7 +581,6 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get callback to determine template customisations for nodes
|
||||
*
|
||||
@ -599,14 +589,14 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
protected function getTreeNodeCustomisations()
|
||||
{
|
||||
$rootTitle = $this->getCMSTreeTitle();
|
||||
return function (SiteTree $node) use ($rootTitle) {
|
||||
return function (DataObject $node) use ($rootTitle) {
|
||||
return [
|
||||
'listViewLink' => $this->LinkListViewChildren($node->ID),
|
||||
'rootTitle' => $rootTitle,
|
||||
'extraClass' => $this->getTreeNodeClasses($node),
|
||||
'Title' => _t(
|
||||
CMSMain::class . '.PAGETYPE_TITLE',
|
||||
'(Page type: {type}) {title}',
|
||||
CMSMain::class . '.RECORD_TYPE_TITLE',
|
||||
'(Record type: {type}) {title}',
|
||||
[
|
||||
'type' => $node->i18n_singular_name(),
|
||||
'title' => $node->Title,
|
||||
@ -617,15 +607,12 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra CSS classes for a page's tree node
|
||||
*
|
||||
* @param SiteTree $node
|
||||
* @return string
|
||||
* Get extra CSS classes for a record's tree node
|
||||
*/
|
||||
public function getTreeNodeClasses(SiteTree $node)
|
||||
public function getTreeNodeClasses(DataObject $node): string
|
||||
{
|
||||
// Get classes from object
|
||||
$classes = $node->CMSTreeClasses();
|
||||
$classes = $node->CMSTreeClasses(); // @TODO that's obviously not a thing.
|
||||
|
||||
// Get status flag classes
|
||||
$flags = $node->getStatusFlags();
|
||||
@ -638,7 +625,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
// Get additional filter classes
|
||||
$filter = $this->getSearchFilter();
|
||||
if ($filter && ($filterClasses = $filter->getPageClasses($node))) {
|
||||
if ($filter && ($filterClasses = $filter->getRecordClasses($node))) { // @TODO rename getRecordClasses or similar (though this is probably part of https://github.com/silverstripe/silverstripe-cms/issues/2949)
|
||||
if (is_array($filterClasses)) {
|
||||
$filterClasses = implode(' ', $filterClasses);
|
||||
}
|
||||
@ -654,8 +641,8 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
*/
|
||||
public function getsubtree(HTTPRequest $request): HTTPResponse
|
||||
{
|
||||
$html = $this->getSiteTreeFor(
|
||||
$this->config()->get('tree_class'),
|
||||
$html = $this->getTreeFor(
|
||||
$this->getModelClass(),
|
||||
$request->getVar('ID'),
|
||||
null,
|
||||
null,
|
||||
@ -687,7 +674,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
$record = $this->getRecord($id);
|
||||
if (!$record) {
|
||||
continue; // In case a page is no longer available
|
||||
continue; // In case a record is no longer available
|
||||
}
|
||||
|
||||
// Create marking set with sole marked root
|
||||
@ -700,10 +687,10 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
// Find the next & previous nodes, for proper positioning (Sort isn't good enough - it's not a raw offset)
|
||||
$prev = null;
|
||||
|
||||
$className = $this->config()->get('tree_class');
|
||||
$className = $this->getModelClass();
|
||||
$next = DataObject::get($className)
|
||||
->filter('ParentID', $record->ParentID)
|
||||
->filter('Sort:GreaterThan', $record->Sort)
|
||||
->filter('Sort:GreaterThan', $record->Sort) // @TODO here's an assumed field
|
||||
->first();
|
||||
|
||||
if (!$next) {
|
||||
@ -750,17 +737,17 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
if (!SecurityToken::inst()->checkRequest($request)) {
|
||||
$this->httpError(400);
|
||||
}
|
||||
if (!$this->CanOrganiseSitetree()) {
|
||||
if (!$this->CanOrganiseTree()) {
|
||||
$this->httpError(
|
||||
403,
|
||||
_t(
|
||||
__CLASS__.'.CANT_REORGANISE',
|
||||
"You do not have permission to rearange the site tree. Your change was not saved."
|
||||
__CLASS__.'.CANT_REORGANISE2',
|
||||
"You do not have permission to rearange the tree. Your change was not saved.",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$className = $this->config()->get('tree_class');
|
||||
$className = $this->getModelClass();
|
||||
$id = $request->requestVar('ID');
|
||||
$parentID = $request->requestVar('ParentID');
|
||||
if (!is_numeric($id) || !is_numeric($parentID)) {
|
||||
@ -768,26 +755,25 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
// Check record exists in the DB
|
||||
/** @var SiteTree $node */
|
||||
$node = DataObject::get_by_id($className, $id);
|
||||
if (!$node) {
|
||||
$this->httpError(
|
||||
500,
|
||||
_t(
|
||||
__CLASS__.'.PLEASESAVE',
|
||||
"Please Save Page: This page could not be updated because it hasn't been saved yet."
|
||||
__CLASS__.'.PLEASESAVE2',
|
||||
"Please Save Record: This record could not be updated because it hasn't been saved yet."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Check top level permissions
|
||||
$root = $node->getParentType();
|
||||
$root = $node->getParentType(); // @TODO generalise. POC has `$node->ParentID == 0 ? 'root' : 'subpage';`
|
||||
if (($parentID == '0' || $root == 'root') && !SiteConfig::current_site_config()->canCreateTopLevel()) {
|
||||
$this->httpError(
|
||||
403,
|
||||
_t(
|
||||
__CLASS__.'.CANT_REORGANISE',
|
||||
"You do not have permission to alter Top level pages. Your change was not saved."
|
||||
__CLASS__.'.CANT_REORGANISE_TOPLEVEL',
|
||||
'You do not have permission to alter Top level records. Your change was not saved.'
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -805,20 +791,20 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$node->write();
|
||||
|
||||
$statusUpdates['modified'][$node->ID] = [
|
||||
'TreeTitle' => $node->TreeTitle
|
||||
'TreeTitle' => $this->getRecordTreeMarkup($node),
|
||||
];
|
||||
|
||||
// Update all dependent pages
|
||||
// Update all dependent pages // @TODO generalise!! We shouldn't reference explicitly page types here.
|
||||
$virtualPages = VirtualPage::get()->filter("CopyContentFromID", $node->ID);
|
||||
foreach ($virtualPages as $virtualPage) {
|
||||
$statusUpdates['modified'][$virtualPage->ID] = [
|
||||
'TreeTitle' => $virtualPage->TreeTitle
|
||||
'TreeTitle' => $this->getRecordTreeMarkup($virtualPage),
|
||||
];
|
||||
}
|
||||
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(__CLASS__.'.REORGANISATIONSUCCESSFUL', 'Reorganised the site tree successfully.') ?? '')
|
||||
rawurlencode(_t(__CLASS__.'.REORGANISATIONSUCCESSFUL2', 'Reorganised the tree successfully.') ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
@ -830,7 +816,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$node->Sort = ++$counter;
|
||||
$node->write();
|
||||
$statusUpdates['modified'][$node->ID] = [
|
||||
'TreeTitle' => $node->TreeTitle
|
||||
'TreeTitle' => $this->getRecordTreeMarkup($node),
|
||||
];
|
||||
} elseif (is_numeric($id)) {
|
||||
// Nodes that weren't "actually moved" shouldn't be registered as
|
||||
@ -846,7 +832,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(__CLASS__.'.REORGANISATIONSUCCESSFUL', 'Reorganised the site tree successfully.') ?? '')
|
||||
rawurlencode(_t(__CLASS__.'.REORGANISATIONSUCCESSFUL2', 'Reorganised the tree successfully.') ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
@ -857,64 +843,43 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current member has the permission to reorganise SiteTree objects.
|
||||
* @return bool
|
||||
* Whether the current member has the permission to reorganise records.
|
||||
*/
|
||||
public function CanOrganiseSitetree()
|
||||
public function CanOrganiseTree(): bool
|
||||
{
|
||||
return Permission::check('SITETREE_REORGANISE');
|
||||
return (bool) Permission::check('SITETREE_REORGANISE'); // @TODO model needs a method or config to say "this is the permission for that!!"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* Whether the tree has been filtered in this request or not.
|
||||
*/
|
||||
public function TreeIsFiltered()
|
||||
public function TreeIsFiltered(): bool
|
||||
{
|
||||
$query = $this->getRequest()->getVar('q');
|
||||
return !empty($query);
|
||||
}
|
||||
|
||||
public function ExtraTreeTools()
|
||||
public function ExtraTreeTools(): string
|
||||
{
|
||||
$html = '';
|
||||
$this->extend('updateExtraTreeTools', $html);
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides information required to generate the search form
|
||||
* and can be modified on extensions through updateSearchContext
|
||||
*
|
||||
* @return \SilverStripe\ORM\Search\SearchContext
|
||||
*/
|
||||
public function getSearchContext()
|
||||
{
|
||||
$context = SiteTree::singleton()->getDefaultSearchContext();
|
||||
|
||||
$this->extend('updateSearchContext', $context);
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search form schema for the current model
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSearchFieldSchema()
|
||||
public function getSearchFieldSchema(): string
|
||||
{
|
||||
$schemaUrl = $this->Link('schema/SearchForm');
|
||||
|
||||
$context = $this->getSearchContext();
|
||||
$singleton = DataObject::singleton($this->getModelClass());
|
||||
$context = $singleton->getDefaultSearchContext();
|
||||
$params = $this->getRequest()->requestVar('q') ?: [];
|
||||
$context->setSearchParams($params);
|
||||
|
||||
$placeholder = _t('SilverStripe\\CMS\\Search\\SearchForm.FILTERLABELTEXT', 'Search') . ' "' .
|
||||
SiteTree::singleton()->i18n_plural_name() . '"';
|
||||
|
||||
$placeholder = _t(SearchForm::class . '.FILTERLABELTEXT2', 'Search "{model}"', ['model' => $singleton->i18n_plural_name()]);
|
||||
$searchParams = $context->getSearchParams();
|
||||
|
||||
$searchParams = array_combine(array_map(function ($key) {
|
||||
return 'Search__' . $key;
|
||||
}, array_keys($searchParams ?? [])), $searchParams ?? []);
|
||||
@ -930,50 +895,59 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Form for page searching for use in templates.
|
||||
* Returns a Form for record searching for use in templates.
|
||||
*
|
||||
* Can be modified from a decorator by a 'updateSearchForm' method
|
||||
*
|
||||
* @return Form
|
||||
*/
|
||||
public function getSearchForm()
|
||||
public function getSearchForm(): Form
|
||||
{
|
||||
$modelClass = $this->getModelClass();
|
||||
$singleton = DataObject::singleton($modelClass);
|
||||
// Create the fields
|
||||
$dateFrom = DateField::create(
|
||||
'Search__LastEditedFrom',
|
||||
_t('SilverStripe\\CMS\\Search\\SearchForm.FILTERDATEFROM', 'From')
|
||||
_t(SearchForm::class . '.FILTERDATEFROM', 'From')
|
||||
)->setLocale(Security::getCurrentUser()->Locale);
|
||||
$dateTo = DateField::create(
|
||||
'Search__LastEditedTo',
|
||||
_t('SilverStripe\\CMS\\Search\\SearchForm.FILTERDATETO', 'To')
|
||||
_t(SearchForm::class . '.FILTERDATETO', 'To')
|
||||
)->setLocale(Security::getCurrentUser()->Locale);
|
||||
$filters = CMSSiteTreeFilter::get_all_filters();
|
||||
// Remove 'All pages' as we set that to empty/default value
|
||||
// Remove 'All records' as we set that to empty/default value
|
||||
unset($filters[CMSSiteTreeFilter_Search::class]);
|
||||
$pageFilter = DropdownField::create(
|
||||
$recordFilter = DropdownField::create(
|
||||
'Search__FilterClass',
|
||||
_t('SilverStripe\\CMS\\Controllers\\CMSMain.PAGES', 'Page status'),
|
||||
_t(SearchForm::class . '.RECORD_STATUS', '{model} status', ['model' => $singleton->i18n_singular_name()]),
|
||||
$filters
|
||||
);
|
||||
$pageFilter->setEmptyString(_t('SilverStripe\\CMS\\Controllers\\CMSMain.PAGESALLOPT', 'All pages'));
|
||||
$pageClasses = DropdownField::create(
|
||||
$recordFilter->setEmptyString(_t(
|
||||
SearchForm::class . '.RECORDS_ALLOPT',
|
||||
'All {model}',
|
||||
['model' => mb_strtolower($singleton->i18n_plural_name())]
|
||||
));
|
||||
$classes = DropdownField::create(
|
||||
'Search__ClassName',
|
||||
_t('SilverStripe\\CMS\\Controllers\\CMSMain.PAGETYPEOPT', 'Page type', 'Dropdown for limiting search to a page type'),
|
||||
$this->getPageTypes()
|
||||
_t(
|
||||
SearchForm::class . '.RECORD_TYPEOPT',
|
||||
'{model} type',
|
||||
'Dropdown for limiting search to a record type',
|
||||
['model' => $singleton->i18n_singular_name()]
|
||||
),
|
||||
$this->getRecordTypes()
|
||||
);
|
||||
$pageClasses->setEmptyString(_t('SilverStripe\\CMS\\Controllers\\CMSMain.PAGETYPEANYOPT', 'Any'));
|
||||
$classes->setEmptyString(_t(SearchForm::class . '.RECORD_TYPEANYOPT', 'Any'));
|
||||
|
||||
// Group the Datefields
|
||||
$dateGroup = FieldGroup::create(
|
||||
_t('SilverStripe\\CMS\\Search\\SearchForm.PAGEFILTERDATEHEADING', 'Last edited'),
|
||||
_t(SearchForm::class . '.RECORD_FILTERDATEHEADING', 'Last edited'),
|
||||
[$dateFrom, $dateTo]
|
||||
)->setName('Search__LastEdited')
|
||||
->addExtraClass('fieldgroup--fill-width');
|
||||
|
||||
// Create the Field list
|
||||
$fields = new FieldList(
|
||||
$pageFilter,
|
||||
$pageClasses,
|
||||
$recordFilter,
|
||||
$classes,
|
||||
$dateGroup
|
||||
);
|
||||
|
||||
@ -1000,18 +974,16 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted array suitable for a dropdown with pagetypes and their translated name
|
||||
*
|
||||
* @return array
|
||||
* Returns a sorted array suitable for a dropdown with classes and their localised name
|
||||
*/
|
||||
protected function getPageTypes()
|
||||
protected function getRecordTypes(): array
|
||||
{
|
||||
$pageTypes = [];
|
||||
foreach (SiteTree::page_type_classes() as $pageTypeClass) {
|
||||
$pageTypes[$pageTypeClass] = SiteTree::singleton($pageTypeClass)->i18n_singular_name();
|
||||
$types = [];
|
||||
foreach (SiteTree::page_type_classes() as $class) { // @TODO generalise!! Not fully into the POC way.
|
||||
$types[$class] = DataObject::singleton($class)->i18n_singular_name();
|
||||
}
|
||||
asort($pageTypes);
|
||||
return $pageTypes;
|
||||
asort($types);
|
||||
return $types;
|
||||
}
|
||||
|
||||
public function doSearch(array $data, Form $form): HTTPResponse
|
||||
@ -1028,7 +1000,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
{
|
||||
$breadcrumbs = $this->Breadcrumbs();
|
||||
if ($breadcrumbs->count() < 2) {
|
||||
return $this->LinkPages();
|
||||
return $this->LinkRecords();
|
||||
}
|
||||
// Get second from end breadcrumb
|
||||
return $breadcrumbs
|
||||
@ -1040,15 +1012,15 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
{
|
||||
$items = ArrayList::create();
|
||||
|
||||
if (($this->getAction() !== 'index') && ($record = $this->currentPage())) {
|
||||
// The page is being edited
|
||||
if (($this->getAction() !== 'index') && ($record = $this->currentRecord())) {
|
||||
// The record is being edited
|
||||
$this->buildEditFormBreadcrumb($items, $record, $unlinked);
|
||||
} else {
|
||||
// Ensure we always have the "Pages" crumb first
|
||||
// Ensure we always have the admin section crumb first
|
||||
$this->pushCrumb(
|
||||
$items,
|
||||
CMSPagesController::menu_title(),
|
||||
$unlinked ? false : $this->LinkPages()
|
||||
CMSMain::menu_title(), // @TODO make static for subclasses to have their own
|
||||
$unlinked ? false : $this->LinkRecords()
|
||||
);
|
||||
|
||||
if ($this->TreeIsFiltered()) {
|
||||
@ -1056,13 +1028,13 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$this->pushCrumb(
|
||||
$items,
|
||||
_t(CMSMain::class . '.SEARCHRESULTS', 'Search results'),
|
||||
($unlinked) ? false : $this->LinkPages()
|
||||
($unlinked) ? false : $this->LinkRecords()
|
||||
);
|
||||
} elseif ($parentID = $this->getRequest()->getVar('ParentID')) {
|
||||
// We're navigating the listview. ParentID is the page whose
|
||||
// We're navigating the listview. ParentID is the record whose
|
||||
// children are currently displayed.
|
||||
if ($page = SiteTree::get()->byID($parentID)) {
|
||||
$this->buildListViewBreadcrumb($items, $page);
|
||||
if ($record = DataObject::get($this->getModelClass())->byID($parentID)) {
|
||||
$this->buildListViewBreadcrumb($items, $record);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1084,30 +1056,32 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Breadcrumb for the Edit page form. Each crumb links back to its own edit form.
|
||||
* Build Breadcrumb for the Edit form. Each crumb links back to its own edit form.
|
||||
*/
|
||||
private function buildEditFormBreadcrumb(ArrayList $items, SiteTree $page, bool $unlinked): void
|
||||
private function buildEditFormBreadcrumb(ArrayList $items, DataObject $record, bool $unlinked): void
|
||||
{
|
||||
// Find all ancestors of the provided page
|
||||
$ancestors = $page->getAncestors(true);
|
||||
// Find all ancestors of the provided record
|
||||
/** @var DataObject&Hierarchy $record */
|
||||
$ancestors = $record->getAncestors(true);
|
||||
$ancestors = array_reverse($ancestors->toArray() ?? []);
|
||||
foreach ($ancestors as $ancestor) {
|
||||
// Link to the ancestor's edit form
|
||||
$this->pushCrumb(
|
||||
$items,
|
||||
$ancestor->getMenuTitle(),
|
||||
$ancestor->getMenuTitle(), // @TODO generalise!!
|
||||
$unlinked ? false : $ancestor->getCMSEditLink()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Breadcrumb for the List view. Each crumb links to the list view for that page.
|
||||
* Build Breadcrumb for the List view. Each crumb links to the list view for that record.
|
||||
*/
|
||||
private function buildListViewBreadcrumb(ArrayList $items, SiteTree $page): void
|
||||
private function buildListViewBreadcrumb(ArrayList $items, DataObject $record): void
|
||||
{
|
||||
// Find all ancestors of the provided page
|
||||
$ancestors = $page->getAncestors(true);
|
||||
// Find all ancestors of the provided record
|
||||
/** @var DataObject&Hierarchy $record */
|
||||
$ancestors = $record->getAncestors(true);
|
||||
$ancestors = array_reverse($ancestors->toArray() ?? []);
|
||||
|
||||
//turns the title and link of the breadcrumbs into template-friendly variables
|
||||
@ -1121,7 +1095,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$params['ParentID'] = $ancestor->ID;
|
||||
$this->pushCrumb(
|
||||
$items,
|
||||
$ancestor->getMenuTitle(),
|
||||
$ancestor->getMenuTitle(), // @TODO generalise!!
|
||||
Controller::join_links($this->Link(), '?' . http_build_query($params ?? []))
|
||||
);
|
||||
}
|
||||
@ -1130,13 +1104,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
/**
|
||||
* Create serialized JSON string with site tree hints data to be injected into
|
||||
* 'data-hints' attribute of root node of jsTree.
|
||||
*
|
||||
* @return string Serialized JSON
|
||||
*/
|
||||
public function SiteTreeHints()
|
||||
public function TreeHints(): string // @TODO rename
|
||||
{
|
||||
$classes = SiteTree::page_type_classes();
|
||||
$memberID = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
|
||||
$classes = SiteTree::page_type_classes(); // @TODO generalise!!
|
||||
$memberID = Security::getCurrentUser()?->ID ?? 0;
|
||||
$cache = $this->getHintsCache();
|
||||
$cacheKey = $this->generateHintsCacheKey($memberID);
|
||||
$json = $cache->get($cacheKey);
|
||||
@ -1154,12 +1126,12 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$def['Root']['disallowedChildren'] = [];
|
||||
|
||||
// Contains all possible classes to support UI controls listing them all,
|
||||
// such as the "add page here" context menu.
|
||||
// such as the "add record here" context menu.
|
||||
$def['All'] = [];
|
||||
|
||||
// Identify disallows and set globals
|
||||
foreach ($classes as $class) {
|
||||
$obj = singleton($class);
|
||||
$obj = DataObject::singleton($class);
|
||||
if ($obj instanceof HiddenClass) {
|
||||
continue;
|
||||
}
|
||||
@ -1170,8 +1142,8 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
];
|
||||
|
||||
// Check if can be created at the root
|
||||
$needsPerm = $obj->config()->get('need_permission');
|
||||
if (!$obj->config()->get('can_be_root')
|
||||
$needsPerm = $obj::config()->get('need_permission');
|
||||
if ($obj::config()->get('can_be_root') === false
|
||||
|| (!array_key_exists($class, $canCreate ?? []) || !$canCreate[$class])
|
||||
|| ($needsPerm && !$this->can($needsPerm))
|
||||
) {
|
||||
@ -1181,18 +1153,18 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
// Hint data specific to the class
|
||||
$def[$class] = [];
|
||||
|
||||
$defaultChild = $obj->defaultChild();
|
||||
if ($defaultChild !== 'Page' && $defaultChild !== null) {
|
||||
$defaultChild = $obj->defaultChild(); // @TODO generalise!!
|
||||
if ($defaultChild !== 'Page' && $defaultChild !== null) { // @TODO Find out where that 'Page' string comes from and fix it
|
||||
$def[$class]['defaultChild'] = $defaultChild;
|
||||
}
|
||||
|
||||
$defaultParent = $obj->defaultParent();
|
||||
$defaultParent = $obj->defaultParent(); // @TODO generalise!!
|
||||
if ($defaultParent !== 1 && $defaultParent !== null) {
|
||||
$def[$class]['defaultParent'] = $defaultParent;
|
||||
}
|
||||
}
|
||||
|
||||
$this->extend('updateSiteTreeHints', $def);
|
||||
$this->extend('updateTreeHints', $def);
|
||||
|
||||
$json = json_encode($def);
|
||||
$cache->set($cacheKey, $json);
|
||||
@ -1202,24 +1174,22 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
/**
|
||||
* Populates an array of classes in the CMS
|
||||
* which allows the user to change the page type.
|
||||
*
|
||||
* @return SS_List
|
||||
* which allows the user to change the record's ClassName field.
|
||||
*/
|
||||
public function PageTypes()
|
||||
public function RecordTypes(): SS_List
|
||||
{
|
||||
$classes = SiteTree::page_type_classes();
|
||||
$classes = SiteTree::page_type_classes(); // @TODO generalise!!
|
||||
|
||||
$result = new ArrayList();
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$instance = SiteTree::singleton($class);
|
||||
$instance = DataObject::singleton($class);
|
||||
if ($instance instanceof HiddenClass) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip this type if it is restricted
|
||||
$needPermissions = $instance->config()->get('need_permission');
|
||||
$needPermissions = $instance::config()->get('need_permission'); // @TODO consider renaming that since it's so vague
|
||||
if ($needPermissions && !$this->can($needPermissions)) {
|
||||
continue;
|
||||
}
|
||||
@ -1227,8 +1197,8 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$result->push(new ArrayData([
|
||||
'ClassName' => $class,
|
||||
'AddAction' => $instance->i18n_singular_name(),
|
||||
'Description' => $instance->i18n_classDescription(),
|
||||
'IconURL' => $instance->getPageIconURL(),
|
||||
'Description' => $instance->i18n_classDescription(), // @TODO generalise!!
|
||||
'IconURL' => $this->getRecordIconUrl($instance),
|
||||
'Title' => $instance->i18n_singular_name(),
|
||||
]));
|
||||
}
|
||||
@ -1238,25 +1208,115 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the icon for this record, if there is one
|
||||
*/
|
||||
public function getRecordIconUrl(DataObject|string $recordOrClass): ?string
|
||||
{
|
||||
if (is_string($recordOrClass)) {
|
||||
return Config::inst()->get($recordOrClass, 'cms_icon');
|
||||
} else {
|
||||
$icon = $recordOrClass::config()->get('cms_icon');
|
||||
}
|
||||
if (!$icon) {
|
||||
return null;
|
||||
}
|
||||
if (strpos($icon ?? '', 'data:image/') !== false) {
|
||||
return $icon;
|
||||
}
|
||||
|
||||
// Icon is relative resource
|
||||
$iconResource = ModuleResourceLoader::singleton()->resolveResource($icon);
|
||||
if ($iconResource instanceof ModuleResource) {
|
||||
return $iconResource->getURL();
|
||||
}
|
||||
|
||||
// Icon is themed resource
|
||||
$iconThemedUrl = ThemeResourceLoader::themedResourceURL($icon);
|
||||
if ($iconThemedUrl) {
|
||||
return $iconThemedUrl;
|
||||
}
|
||||
|
||||
// Full path to file
|
||||
if (Director::fileExists($icon)) {
|
||||
return ModuleResourceLoader::resourceURL($icon);
|
||||
}
|
||||
|
||||
// Skip invalid files
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS class for the record's icon, if there is one
|
||||
*/
|
||||
public function getRecordIconCssClass(DataObject|string $recordOrClass): ?string
|
||||
{
|
||||
if (is_string($recordOrClass)) {
|
||||
return Config::inst()->get($recordOrClass, 'cms_icon_class');
|
||||
}
|
||||
return $recordOrClass::config()->get('cms_icon_class');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML markup to represent the record in a jstree structure.
|
||||
*
|
||||
* Returns three <span> html elements, an empty <span> with the class 'jstree-recordicon' in
|
||||
* front, following by a <span> wrapping around its MenuTitle, then following by a <span> indicating its
|
||||
* publication status.
|
||||
*/
|
||||
public function getRecordTreeMarkup(DataObject $record): string
|
||||
{
|
||||
$children = $this->creatableChildPages($record);
|
||||
$flags = $record->getStatusFlags();
|
||||
$iconClasses = [
|
||||
'jstree-recordicon',
|
||||
'record-icon',
|
||||
$this->getRecordIconCssClass($record), // @TODO used to use cms_icon too, but that can hold non-css classes!! See what happens now with custom icon file
|
||||
'class-' . Convert::raw2htmlid(get_class($record)),
|
||||
];
|
||||
$record->invokeWithExtensions('updateTreeIconClasses', $iconClasses);
|
||||
$treeTitle = sprintf(
|
||||
'<span class="%s"></span><span class="item" data-allowedchildren="%s">%s</span>',
|
||||
implode(' ', $iconClasses),
|
||||
Convert::raw2att(json_encode($children)),
|
||||
Convert::raw2xml(str_replace(["\n","\r"], "", $record->getTreeTitle()))
|
||||
);
|
||||
foreach ($flags as $class => $data) {
|
||||
if (is_string($data)) {
|
||||
$data = ['text' => $data];
|
||||
}
|
||||
$treeTitle .= sprintf(
|
||||
'<span class="badge %s"%s>%s</span>',
|
||||
'status-' . Convert::raw2xml($class),
|
||||
(isset($data['title'])) ? sprintf(' title="%s"', Convert::raw2xml($data['title'])) : '',
|
||||
Convert::raw2xml($data['text'])
|
||||
);
|
||||
}
|
||||
|
||||
return $treeTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database record to be managed by the CMS.
|
||||
*
|
||||
* @param int $id Record ID
|
||||
* @param int $versionID optional Version id of the given record
|
||||
* @return SiteTree
|
||||
*/
|
||||
public function getRecord($id, $versionID = null)
|
||||
public function getRecord($id, ?int $versionID = null): ?DataObject
|
||||
{
|
||||
if (!$id) {
|
||||
return null;
|
||||
}
|
||||
$treeClass = $this->config()->get('tree_class');
|
||||
if ($id instanceof $treeClass) {
|
||||
$modelClass = $this->getModelClass();
|
||||
if ($id instanceof $modelClass) {
|
||||
return $id;
|
||||
}
|
||||
if (substr($id ?? '', 0, 3) == 'new') {
|
||||
return $this->getNewItem($id);
|
||||
}
|
||||
if ($id === 'singleton') {
|
||||
return DataObject::singleton($modelClass);
|
||||
}
|
||||
if (!is_numeric($id)) {
|
||||
return null;
|
||||
}
|
||||
@ -1267,25 +1327,28 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$versionID = (int) $this->getRequest()->getVar('Version');
|
||||
}
|
||||
|
||||
/** @var SiteTree $record */
|
||||
$isVersioned = $modelClass::has_extension(Versioned::class);
|
||||
if ($versionID) {
|
||||
$record = Versioned::get_version($treeClass, $id, $versionID);
|
||||
if (!$isVersioned) {
|
||||
throw new HTTPResponse_Exception("Cannot get a version of non-versioned $modelClass record", 400);
|
||||
}
|
||||
$record = Versioned::get_version($modelClass, $id, $versionID);
|
||||
} else {
|
||||
$record = DataObject::get_by_id($treeClass, $id);
|
||||
$record = DataObject::get_by_id($modelClass, $id);
|
||||
}
|
||||
|
||||
// Then, try getting a record from the live site
|
||||
if (!$record) {
|
||||
// $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id");
|
||||
if (!$record && $isVersioned) {
|
||||
// $record = Versioned::get_one_by_stage($modelClass, "Live", "\"$modelClass\".\"ID\" = $id");
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
singleton($treeClass)->flushCache();
|
||||
DataObject::singleton($modelClass)->flushCache();
|
||||
|
||||
$record = DataObject::get_by_id($treeClass, $id);
|
||||
$record = DataObject::get_by_id($modelClass, $id);
|
||||
}
|
||||
|
||||
// Then, try getting a deleted record
|
||||
if (!$record) {
|
||||
$record = Versioned::get_latest_version($treeClass, $id);
|
||||
if (!$record && $isVersioned) {
|
||||
$record = Versioned::get_latest_version($modelClass, $id);
|
||||
}
|
||||
|
||||
// Set the reading mode back to what it was.
|
||||
@ -1298,11 +1361,10 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @return Form
|
||||
*/
|
||||
public function EditForm($request = null)
|
||||
public function EditForm($request = null): Form
|
||||
{
|
||||
// set page ID from request
|
||||
// set record ID from request
|
||||
if ($request) {
|
||||
// Validate id is present
|
||||
$id = $request->param('ID');
|
||||
@ -1310,7 +1372,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$this->httpError(400);
|
||||
return null;
|
||||
}
|
||||
$this->setCurrentPageID($id);
|
||||
$this->setCurrentRecordID($id);
|
||||
}
|
||||
return $this->getEditForm();
|
||||
}
|
||||
@ -1318,13 +1380,12 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
/**
|
||||
* @param int $id
|
||||
* @param FieldList $fields
|
||||
* @return Form
|
||||
*/
|
||||
public function getEditForm($id = null, $fields = null)
|
||||
public function getEditForm($id = null, $fields = null): Form
|
||||
{
|
||||
// Get record
|
||||
if (!$id) {
|
||||
$id = $this->currentPageID();
|
||||
$id = $this->currentRecordID();
|
||||
}
|
||||
$record = $this->getRecord($id);
|
||||
|
||||
@ -1339,29 +1400,12 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
// Add extra fields
|
||||
$deletedFromStage = !$record->isOnDraft();
|
||||
$fields->push($idField = new HiddenField("ID", false, $id));
|
||||
// Necessary for different subsites
|
||||
$fields->push($liveLinkField = new HiddenField("AbsoluteLink", false, $record->AbsoluteLink()));
|
||||
$fields->push($liveLinkField = new HiddenField("LiveLink"));
|
||||
$fields->push($stageLinkField = new HiddenField("StageLink"));
|
||||
$fields->push($archiveWarningMsgField = new HiddenField("ArchiveWarningMessage"));
|
||||
$fields->push(new HiddenField("TreeTitle", false, $record->getTreeTitle()));
|
||||
$fields->push(new HiddenField('ID', false, $id));
|
||||
$fields->push($archiveWarningMsgField = new HiddenField('ArchiveWarningMessage'));
|
||||
$fields->push(new HiddenField('TreeTitle', false, $this->getRecordTreeMarkup($record))); // @TODO do we need this?
|
||||
|
||||
$archiveWarningMsgField->setValue($this->getArchiveWarningMessage($record));
|
||||
|
||||
// Build preview / live links
|
||||
$liveLink = $record->getAbsoluteLiveLink();
|
||||
if ($liveLink) {
|
||||
$liveLinkField->setValue($liveLink);
|
||||
}
|
||||
if (!$deletedFromStage) {
|
||||
$stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
|
||||
if ($stageLink) {
|
||||
$stageLinkField->setValue($stageLink);
|
||||
}
|
||||
}
|
||||
|
||||
// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
|
||||
if (($record instanceof CMSPreviewable || $record->has_extension(CMSPreviewable::class))
|
||||
&& !$fields->fieldByName('SilverStripeNavigator')
|
||||
@ -1420,7 +1464,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
$form->addExtraClass('fill-height flexbox-area-grow');
|
||||
|
||||
if (!$record->canEdit() || $deletedFromStage) {
|
||||
if (!$record->canEdit() || ($record->hasExtension(Versioned::class) && $record->hasStages() && !$record->isOnDraft())) {
|
||||
$readonlyFields = $form->Fields()->makeReadonly();
|
||||
$form->setFields($readonlyFields);
|
||||
}
|
||||
@ -1437,10 +1481,10 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function EmptyForm()
|
||||
public function EmptyForm(): Form
|
||||
{
|
||||
$fields = new FieldList(
|
||||
new LabelField('PageDoesntExistLabel', _t('SilverStripe\\CMS\\Controllers\\CMSMain.PAGENOTEXISTS', "This page doesn't exist"))
|
||||
new LabelField('RecordDoesntExistLabel', _t(__CLASS__ . '.RECORDNOTEXISTS', "This record doesn't exist"))
|
||||
);
|
||||
$form = parent::EmptyForm();
|
||||
$form->setFields($fields);
|
||||
@ -1449,39 +1493,32 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an archive warning message based on the page's children
|
||||
*
|
||||
* @param SiteTree $record
|
||||
* @return string
|
||||
* Build an archive warning message based on the record's children
|
||||
*/
|
||||
/**
|
||||
* Build an archive warning message based on the page's children
|
||||
*
|
||||
* @param SiteTree $record
|
||||
* @return string
|
||||
*/
|
||||
protected function getArchiveWarningMessage($record)
|
||||
protected function getArchiveWarningMessage(DataObject $record): string
|
||||
{
|
||||
|
||||
$defaultMessage = _t('SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarningWithChildren', 'Warning: This page and all of its child pages will be unpublished before being sent to the archive.\n\nAre you sure you want to proceed?');
|
||||
$defaultMessage = _t(
|
||||
LeftAndMain::class . '.ArchiveWarningWithChildren',
|
||||
'Warning: This record and all of its child records will be unpublished before being sent to the archive.\n\nAre you sure you want to proceed?'
|
||||
);
|
||||
|
||||
// Option to disable this feature as it is slow on large sites
|
||||
if (!$this->config()->enable_dynamic_archive_warning_message) {
|
||||
if (!static::config()->get('enable_dynamic_archive_warning_message')) {
|
||||
return $defaultMessage;
|
||||
}
|
||||
|
||||
// Get all page's descendants
|
||||
// Get all record's descendants
|
||||
$descendants = [];
|
||||
$this->collateDescendants([$record->ID], $descendants);
|
||||
if (!$descendants) {
|
||||
$descendants = [];
|
||||
}
|
||||
|
||||
// Get the IDs of all changeset including at least one of the pages.
|
||||
// Get the IDs of all changeset including at least one of the records.
|
||||
$descendants[] = $record->ID;
|
||||
$inChangeSetIDs = ChangeSetItem::get()->filter([
|
||||
'ObjectID' => $descendants,
|
||||
'ObjectClass' => SiteTree::class
|
||||
'ObjectClass' => $this->getModelClass(),
|
||||
])->column('ChangeSetID');
|
||||
|
||||
// Count number of affected change set
|
||||
@ -1496,20 +1533,31 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$numCampaigns = mb_strtolower($numCampaigns ?? '');
|
||||
|
||||
if (count($descendants ?? []) > 0 && $affectedChangeSetCount > 0) {
|
||||
$archiveWarningMsg = _t('SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarningWithChildrenAndCampaigns', 'Warning: This page and all of its child pages will be unpublished and automatically removed from their associated {NumCampaigns} before being sent to the archive.\n\nAre you sure you want to proceed?', [ 'NumCampaigns' => $numCampaigns ]);
|
||||
$archiveWarningMsg = _t(
|
||||
LeftAndMain::class . '.ArchiveWarningWithChildrenAndCampaigns',
|
||||
'Warning: This record and all of its child records will be unpublished and automatically removed from their associated {NumCampaigns} before being sent to the archive.\n\nAre you sure you want to proceed?',
|
||||
[ 'NumCampaigns' => $numCampaigns ]
|
||||
);
|
||||
} elseif (count($descendants ?? []) > 0) {
|
||||
$archiveWarningMsg = $defaultMessage;
|
||||
} elseif ($affectedChangeSetCount > 0) {
|
||||
$archiveWarningMsg = _t('SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarningWithCampaigns', 'Warning: This page will be unpublished and automatically removed from their associated {NumCampaigns} before being sent to the archive.\n\nAre you sure you want to proceed?', [ 'NumCampaigns' => $numCampaigns ]);
|
||||
$archiveWarningMsg = _t(
|
||||
LeftAndMain::class . '.ArchiveWarningWithCampaigns',
|
||||
'Warning: This record will be unpublished and automatically removed from their associated {NumCampaigns} before being sent to the archive.\n\nAre you sure you want to proceed?',
|
||||
[ 'NumCampaigns' => $numCampaigns ]
|
||||
);
|
||||
} else {
|
||||
$archiveWarningMsg = _t('SilverStripe\\CMS\\Controllers\\CMSMain.ArchiveWarning', 'Warning: This page will be unpublished before being sent to the archive.\n\nAre you sure you want to proceed?');
|
||||
$archiveWarningMsg = _t(
|
||||
LeftAndMain::class . '.ArchiveWarning',
|
||||
'Warning: This record will be unpublished before being sent to the archive.\n\nAre you sure you want to proceed?'
|
||||
);
|
||||
}
|
||||
|
||||
return $archiveWarningMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find IDs of all descendant pages for the provided ID lists.
|
||||
* Find IDs of all descendant records for the provided ID lists.
|
||||
* @param int[] $recordIDs
|
||||
* @param array $collator
|
||||
* @return bool
|
||||
@ -1517,7 +1565,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
protected function collateDescendants($recordIDs, &$collator)
|
||||
{
|
||||
|
||||
$children = SiteTree::get()->filter(['ParentID' => $recordIDs])->column();
|
||||
$children = DataObject::get($this->getModelClass())->filter(['ParentID' => $recordIDs])->column();
|
||||
if ($children) {
|
||||
foreach ($children as $item) {
|
||||
$collator[] = $item;
|
||||
@ -1528,10 +1576,9 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method exclusively handles deferred ajax requests to render the
|
||||
* pages tree deferred handler (no pjax-fragment)
|
||||
* records tree deferred handler (no pjax-fragment)
|
||||
*
|
||||
* @return DBHTMLText HTML response with the rendered treeview
|
||||
*/
|
||||
@ -1569,21 +1616,21 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to request the list of page types allowed under a given page instance.
|
||||
* Provides a slower but more precise response over SiteTreeHints
|
||||
* Callback to request the list of record types allowed under a given record instance.
|
||||
* Provides a slower but more precise response over TreeHints
|
||||
*/
|
||||
public function childfilter(HTTPRequest $request): HTTPResponse
|
||||
{
|
||||
// Check valid parent specified
|
||||
$parentID = $request->requestVar('ParentID');
|
||||
$parent = SiteTree::get()->byID($parentID);
|
||||
$parent = DataObject::get($this->getModelClass())->byID($parentID);
|
||||
if (!$parent || !$parent->exists()) {
|
||||
$this->httpError(404);
|
||||
}
|
||||
|
||||
// Build hints specific to this class
|
||||
// Identify disallows and set globals
|
||||
$classes = SiteTree::page_type_classes();
|
||||
$classes = SiteTree::page_type_classes(); // @TODO generalise!!
|
||||
$disallowedChildren = [];
|
||||
foreach ($classes as $class) {
|
||||
$obj = singleton($class);
|
||||
@ -1623,8 +1670,8 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
|
||||
* defaulting to no filter and show all pages in first level.
|
||||
* Returns the records meet a certain criteria as {@see CMSSiteTreeFilter} or the subrecords of a parent record
|
||||
* defaulting to no filter and show all records in first level.
|
||||
* Doubles as search results, if any search parameters are set through {@link SearchForm()}.
|
||||
*
|
||||
* @param array $params Search filter criteria
|
||||
@ -1637,7 +1684,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
if ($filter = $this->getQueryFilter($params)) {
|
||||
return $filter->getFilteredPages();
|
||||
} else {
|
||||
$list = DataList::create($this->config()->get('tree_class'));
|
||||
$list = DataObject::get($this->getModelClass());
|
||||
$parentID = is_numeric($parentID) ? $parentID : 0;
|
||||
return $list->filter("ParentID", $parentID);
|
||||
}
|
||||
@ -1658,7 +1705,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$gridFieldConfig = GridFieldConfig::create()->addComponents(
|
||||
Injector::inst()->create(GridFieldSortableHeader::class),
|
||||
Injector::inst()->create(GridFieldDataColumns::class),
|
||||
Injector::inst()->createWithArgs(GridFieldPaginator::class, [$this->config()->get('page_length')])
|
||||
Injector::inst()->createWithArgs(GridFieldPaginator::class, [static::config()->get('page_length')])
|
||||
);
|
||||
if ($parentID) {
|
||||
$linkSpec = $this->LinkListViewChildren('%d');
|
||||
@ -1667,17 +1714,18 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
->setLinkSpec($linkSpec)
|
||||
->setAttributes(['data-pjax-target' => 'ListViewForm,Breadcrumbs'])
|
||||
);
|
||||
$this->setCurrentPageID($parentID);
|
||||
$this->setCurrentRecordID($parentID);
|
||||
}
|
||||
$gridField = GridField::create('Page', 'Pages', $list, $gridFieldConfig);
|
||||
$gridField = GridField::create('Record', 'Records', $list, $gridFieldConfig); // @TODO make title string i18n
|
||||
$gridField->setAttribute('cms-loading-ignore-url-params', true);
|
||||
$columns = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
|
||||
|
||||
// Don't allow navigating into children nodes on filtered lists
|
||||
// Set up columns and sorting for list view GridField
|
||||
$modelClass = $this->getModelClass();
|
||||
$fields = [
|
||||
'getTreeTitle' => _t('SilverStripe\\CMS\\Model\\SiteTree.PAGETITLE', 'Page Title'),
|
||||
'i18n_singular_name' => _t('SilverStripe\\CMS\\Model\\SiteTree.PAGETYPE', 'Page Type'),
|
||||
'LastEdited' => _t('SilverStripe\\CMS\\Model\\SiteTree.LASTUPDATED', 'Last Updated'),
|
||||
'getTreeTitle' => _t($modelClass . '.TREETITLE', 'Title'),
|
||||
'i18n_singular_name' => _t($modelClass . '.TREETYPE', 'Record Type'),
|
||||
'LastEdited' => _t($modelClass . '.LASTUPDATED', 'Last Updated'),
|
||||
];
|
||||
$sortableHeader = $gridField->getConfig()->getComponentByType(GridFieldSortableHeader::class);
|
||||
$sortableHeader->setFieldSorting(['getTreeTitle' => 'Title']);
|
||||
@ -1696,24 +1744,24 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
$columns->setFieldFormatting([
|
||||
'listChildrenLink' => function ($value, &$item) {
|
||||
/** @var SiteTree $item */
|
||||
$num = $item ? $item->numChildren() : null;
|
||||
/** @var DataObject&Hierarchy $item */
|
||||
$num = $item?->numChildren();
|
||||
if ($num) {
|
||||
return sprintf(
|
||||
'<a class="btn btn-secondary btn--no-text btn--icon-large font-icon-right-dir cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s"><span class="sr-only">%s child pages</span></a>',
|
||||
'<a class="btn btn-secondary btn--no-text btn--icon-large font-icon-right-dir cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s"><span class="sr-only">%s child pages</span></a>', // @TODO generalise and i18n-ify
|
||||
$this->LinkListViewChildren((int)$item->ID),
|
||||
$num
|
||||
);
|
||||
}
|
||||
},
|
||||
'getTreeTitle' => function ($value, &$item) {
|
||||
/** @var SiteTree $item */
|
||||
/** @var DataObject $item */
|
||||
$title = sprintf(
|
||||
'<a class="action-detail" href="%s">%s</a>',
|
||||
$item->getCMSEditLink(),
|
||||
$item->TreeTitle // returns HTML, does its own escaping
|
||||
$this->getRecordTreeMarkup($item) // returns HTML, does its own escaping
|
||||
);
|
||||
$breadcrumbs = $item->Breadcrumbs(20, true, false, true, '/');
|
||||
$breadcrumbs = $item->Breadcrumbs(20, true, false, true, '/'); // @TODO generalise!!
|
||||
// Remove item's tile
|
||||
$breadcrumbs = preg_replace('/[^\/]+$/', '', trim($breadcrumbs ?? ''));
|
||||
// Trim spaces around delimiters
|
||||
@ -1748,31 +1796,69 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return $listview;
|
||||
}
|
||||
|
||||
public function currentPageID()
|
||||
public function currentRecordID()
|
||||
{
|
||||
$id = parent::currentPageID();
|
||||
|
||||
$this->extend('updateCurrentPageID', $id);
|
||||
|
||||
$id = parent::currentRecordID();
|
||||
$this->extend('updateCurrentRecordID', $id);
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of the classes that can be created under this specific record.
|
||||
*
|
||||
* @return array Array of associative arrays with FQCNs, localised model names, and icon CSS classes.
|
||||
*/
|
||||
protected function creatableChildPages(DataObject $record): array
|
||||
{
|
||||
// Build the list of candidate children
|
||||
$cache = $this->getCreatableChildrenCache();
|
||||
$cacheKey = $this->generateChildrenCacheKey();
|
||||
$children = $cache->get($cacheKey, []);
|
||||
$modelClass = $this->getModelClass();
|
||||
|
||||
if (!$children || !isset($children[$modelClass][$record->ID])) {
|
||||
$children[$modelClass][$record->ID] = [];
|
||||
$candidates = $this->getValidSubClasses();
|
||||
|
||||
foreach ($candidates as $childClass) {
|
||||
$child = DataObject::singleton($childClass);
|
||||
if ($child->canCreate(context: ['Parent' => $record])) {
|
||||
$children[$modelClass][$record->ID][] = [
|
||||
'ClassName' => $childClass,
|
||||
'Title' => $child->i18n_singular_name(),
|
||||
'IconClass' => $this->getRecordIconCssClass($child),
|
||||
];
|
||||
}
|
||||
}
|
||||
$cache->set($cacheKey, $children);
|
||||
}
|
||||
|
||||
return $children[$modelClass][$record->ID];
|
||||
}
|
||||
|
||||
protected function getValidSubClasses(): array
|
||||
{
|
||||
$modelClass = $this->getModelClass();
|
||||
$classes = ClassInfo::getValidSubClasses($modelClass);
|
||||
DataObject::singleton()->invokeWithExtensions('updateValidSubClasses', $classes);
|
||||
return $classes;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------//
|
||||
// Data saving handlers
|
||||
|
||||
/**
|
||||
* Save and Publish page handler
|
||||
* Save and Publish record handler
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function save(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$className = $this->config()->get('tree_class');
|
||||
$className = $this->getModelClass();
|
||||
|
||||
// Existing or new record?
|
||||
$id = $data['ID'];
|
||||
if (substr($id ?? '', 0, 3) != 'new') {
|
||||
/** @var SiteTree $record */
|
||||
$record = DataObject::get_by_id($className, $id);
|
||||
// Check edit permissions
|
||||
if ($record && !$record->canEdit()) {
|
||||
@ -1790,14 +1876,15 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
// Check publishing permissions
|
||||
$doPublish = !empty($data['publish']);
|
||||
if ($record && $doPublish && !$record->canPublish()) {
|
||||
$isVersioned = $record->hasExtension(Versioned::class);
|
||||
if ($isVersioned && $doPublish && !$record->canPublish()) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
|
||||
$record->HasBrokenLink = 0;
|
||||
$record->HasBrokenFile = 0;
|
||||
$record->HasBrokenLink = 0; // @TODO generalise!!
|
||||
$record->HasBrokenFile = 0; // @TODO generalise!!
|
||||
|
||||
if (!$record->ObsoleteClassName) {
|
||||
if ($isVersioned && !$record->ObsoleteClassName) { // @TODO I think that's specific to SiteTree??
|
||||
$record->writeWithoutVersion();
|
||||
}
|
||||
|
||||
@ -1812,19 +1899,28 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$form->saveInto($record);
|
||||
$record->write();
|
||||
|
||||
// If the 'Publish' button was clicked, also publish the page
|
||||
// If the 'Publish' button was clicked, also publish the record
|
||||
if ($doPublish) {
|
||||
if (!$record->hasExtension(RecursivePublishable::class)) {
|
||||
throw new HTTPResponse_Exception(get_class($record) . ' record is not publishable.', 400);
|
||||
}
|
||||
$record->publishRecursive();
|
||||
$message = _t(
|
||||
__CLASS__ . '.PUBLISHED',
|
||||
"Published '{title}' successfully.",
|
||||
['title' => $record->Title]
|
||||
LeftAndMain::class . '.PUBLISHED_RECORD',
|
||||
'Published {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$message = _t(
|
||||
__CLASS__ . '.SAVED',
|
||||
"Saved '{title}' successfully.",
|
||||
['title' => $record->Title]
|
||||
LeftAndMain::class . '.SAVED_RECORD',
|
||||
'Saved {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@ -1835,12 +1931,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
/**
|
||||
* @param int|string $id
|
||||
* @param bool $setID
|
||||
* @return mixed|DataObject
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function getNewItem($id, $setID = true)
|
||||
public function getNewItem($id, $setID = true): DataObject
|
||||
{
|
||||
$parentClass = $this->config()->get('tree_class');
|
||||
$parentClass = $this->getModelClass();
|
||||
list(, $className, $parentID) = array_pad(explode('-', $id ?? ''), 3, null);
|
||||
|
||||
if (!is_a($className, $parentClass ?? '', true)) {
|
||||
@ -1851,23 +1946,20 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
throw new HTTPResponse_Exception($response);
|
||||
}
|
||||
|
||||
/** @var SiteTree $newItem */
|
||||
/** @var DataObject $newItem */
|
||||
$newItem = Injector::inst()->create($className);
|
||||
$newItem->Title = _t(
|
||||
__CLASS__ . '.NEWPAGE',
|
||||
"New {pagetype}",
|
||||
'followed by a page type title',
|
||||
['pagetype' => singleton($className)->i18n_singular_name()]
|
||||
LeftAndMain::class . '.NEW_RECORD',
|
||||
'New {recordtype}',
|
||||
['recordtype' => DataObject::singleton($className)->i18n_singular_name()]
|
||||
);
|
||||
$newItem->ClassName = $className;
|
||||
$newItem->ParentID = $parentID;
|
||||
|
||||
// DataObject::fieldExists only checks the current class, not the hierarchy
|
||||
// This allows the CMS to set the correct sort value
|
||||
if ($newItem->castingHelper('Sort')) {
|
||||
$table = DataObject::singleton(SiteTree::class)->baseTable();
|
||||
if ($newItem->hasDatabaseField('Sort')) { // @TODO Let the field name be configurable
|
||||
$table = DataObject::singleton($parentClass)->baseTable();
|
||||
$maxSort = DB::prepared_query(
|
||||
"SELECT MAX(\"Sort\") FROM \"$table\" WHERE \"ParentID\" = ?",
|
||||
"SELECT MAX(\"Sort\") FROM \"$table\" WHERE \"ParentID\" = ?", // @TODO this won't work if sort is on a subclass! Use ORM instead
|
||||
[$parentID]
|
||||
)->value();
|
||||
$newItem->Sort = (int)$maxSort + 1;
|
||||
@ -1878,55 +1970,43 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
# Some modules like subsites add extra fields that need to be set when the new item is created
|
||||
$this->extend('augmentNewSiteTreeItem', $newItem);
|
||||
$this->extend('updateNewItem', $newItem); // @TODO rename
|
||||
|
||||
return $newItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually perform the publication step
|
||||
*
|
||||
* @param Versioned|DataObject $record
|
||||
* @return mixed
|
||||
*/
|
||||
public function performPublish($record)
|
||||
{
|
||||
if ($record && !$record->canPublish()) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
|
||||
$record->publishRecursive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts a page by publishing it to live.
|
||||
* Use {@link restorepage()} if you want to restore a page
|
||||
* Reverts a record by publishing it to live.
|
||||
* Use {@link restoreRecord()} if you want to restore a record
|
||||
* which was deleted from draft without publishing.
|
||||
*
|
||||
* @uses SiteTree->doRevertToLive()
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function revert(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$modelClass = $this->getModelClass();
|
||||
if (!isset($data['ID'])) {
|
||||
throw new HTTPResponse_Exception("Please pass an ID in the form content", 400);
|
||||
}
|
||||
|
||||
$id = (int) $data['ID'];
|
||||
$restoredPage = Versioned::get_latest_version(SiteTree::class, $id);
|
||||
if (!$restoredPage) {
|
||||
throw new HTTPResponse_Exception("SiteTree #$id not found", 400);
|
||||
if (!$modelClass::has_extension(Versioned::class)) {
|
||||
throw new HTTPResponse_Exception("$modelClass record cannot be reverted", 400);
|
||||
}
|
||||
|
||||
$table = DataObject::singleton(SiteTree::class)->baseTable();
|
||||
$liveTable = DataObject::singleton(SiteTree::class)->stageTable($table, Versioned::LIVE);
|
||||
$record = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, [
|
||||
$id = (int) $data['ID'];
|
||||
$restoredRecord = Versioned::get_latest_version($modelClass, $id);
|
||||
if (!$restoredRecord) {
|
||||
throw new HTTPResponse_Exception("Record #$id not found", 400);
|
||||
}
|
||||
|
||||
$table = DataObject::singleton($modelClass)->baseTable();
|
||||
$liveTable = DataObject::singleton($modelClass)->stageTable($table, Versioned::LIVE);
|
||||
$record = Versioned::get_one_by_stage($modelClass, Versioned::LIVE, [
|
||||
"\"$liveTable\".\"ID\"" => $id
|
||||
]);
|
||||
|
||||
// a user can restore a page without publication rights, as it just adds a new draft state
|
||||
// (this action should just be available when page has been "deleted from draft")
|
||||
// a user can restore a record without publication rights, as it just adds a new draft state
|
||||
// (this action should just be available when the record has been "deleted from draft")
|
||||
if ($record && !$record->canEdit()) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
@ -1939,27 +2019,27 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(
|
||||
__CLASS__ . '.RESTORED',
|
||||
"Restored '{title}' successfully",
|
||||
'Param {title} is a title',
|
||||
['title' => $record->Title]
|
||||
) ?? '')
|
||||
LeftAndMain::class . '.RESTORED_RECORD',
|
||||
'Restored {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
return $this->getResponseNegotiator()->respond($this->getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current page from draft stage.
|
||||
*
|
||||
* @see deletefromlive()
|
||||
* Delete the current record from draft stage.
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function delete(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$id = $data['ID'];
|
||||
$record = SiteTree::get()->byID($id);
|
||||
$record = DataObject::get($this->getModelClass())->byID($id);
|
||||
if ($record && !$record->canDelete()) {
|
||||
return Security::permissionFailure();
|
||||
}
|
||||
@ -1970,13 +2050,28 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
// Delete record
|
||||
$record->delete();
|
||||
|
||||
if ($record->hasExtension(Versioned::class) && $record->hasStages()) {
|
||||
$message = _t(
|
||||
LeftAndMain::class . '.ARCHIVED_RECORD',
|
||||
'Archived {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$message = _t(
|
||||
LeftAndMain::class . '.DELETED_RECORD',
|
||||
'Deleted {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
);
|
||||
}
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(
|
||||
__CLASS__ . '.REMOVEDPAGEFROMDRAFT',
|
||||
"Removed '{title}' from the draft site",
|
||||
['title' => $record->Title]
|
||||
) ?? '')
|
||||
rawurlencode($message)
|
||||
);
|
||||
|
||||
// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
|
||||
@ -1984,14 +2079,14 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this page from both live and stage
|
||||
* Delete this record from both live and stage
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function archive(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$id = $data['ID'];
|
||||
$record = SiteTree::get()->byID($id);
|
||||
$record = DataObject::get($this->getModelClass())->byID($id);
|
||||
if (!$record || !$record->exists()) {
|
||||
throw new HTTPResponse_Exception("Bad record ID #$id", 404);
|
||||
}
|
||||
@ -2005,10 +2100,13 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(
|
||||
__CLASS__ . '.ARCHIVEDPAGE',
|
||||
"Archived page '{title}'",
|
||||
['title' => $record->Title]
|
||||
) ?? '')
|
||||
LeftAndMain::class . '.ARCHIVED_RECORD',
|
||||
'Archived {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
|
||||
@ -2024,10 +2122,13 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
public function unpublish(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$className = $this->config()->get('tree_class');
|
||||
/** @var SiteTree $record */
|
||||
$className = $this->getModelClass();
|
||||
$record = DataObject::get_by_id($className, $data['ID']);
|
||||
|
||||
if (!$record->hasExtension(Versioned::class)) {
|
||||
throw new HTTPResponse_Exception(get_class($record) . ' record cannot be unpublished.', 400);
|
||||
}
|
||||
|
||||
if ($record && !$record->canUnpublish()) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
@ -2040,10 +2141,13 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(
|
||||
__CLASS__ . '.REMOVEDPAGE',
|
||||
"Removed '{title}' from the published site",
|
||||
['title' => $record->Title]
|
||||
) ?? '')
|
||||
LeftAndMain::class . '.UNPUBLISHED_RECORD',
|
||||
'Unpublished {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
return $this->getResponseNegotiator()->respond($this->getRequest());
|
||||
@ -2055,7 +2159,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
public function rollback()
|
||||
{
|
||||
return $this->doRollback([
|
||||
'ID' => $this->currentPageID(),
|
||||
'ID' => $this->currentRecordID(),
|
||||
'Version' => $this->getRequest()->param('VersionID')
|
||||
], null);
|
||||
}
|
||||
@ -2074,8 +2178,13 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
|
||||
$version = (isset($data['Version'])) ? (int) $data['Version'] : null;
|
||||
|
||||
/** @var SiteTree|Versioned $record */
|
||||
$record = Versioned::get_latest_version($this->config()->get('tree_class'), $id);
|
||||
$modelClass = $this->getModelClass();
|
||||
if (!$modelClass::has_extension(Versioned::class)) {
|
||||
throw new HTTPResponse_Exception("$modelClass record cannot be rolled back", 400);
|
||||
}
|
||||
|
||||
/** @var DataObject&Versioned $record */
|
||||
$record = Versioned::get_latest_version($modelClass, $id);
|
||||
if ($record && !$record->canEdit()) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
@ -2083,22 +2192,22 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
if ($version) {
|
||||
$record->rollbackRecursive($version);
|
||||
$message = _t(
|
||||
__CLASS__ . '.ROLLEDBACKVERSIONv2',
|
||||
"Rolled back to version #{version}.",
|
||||
LeftAndMain::class . '.ROLLEDBACK_VERSION',
|
||||
'Rolled back to version #{version}.',
|
||||
['version' => $data['Version']]
|
||||
);
|
||||
} else {
|
||||
$record->doRevertToLive();
|
||||
$record->publishRecursive();
|
||||
$message = _t(
|
||||
__CLASS__ . '.ROLLEDBACKPUBv2',
|
||||
"Rolled back to published version."
|
||||
LeftAndMain::class . '.ROLLEDBACK_PUBLISHED',
|
||||
'Rolled back to published version.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->getResponse()->addHeader('X-Status', rawurlencode($message ?? ''));
|
||||
|
||||
// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
|
||||
// Can be used in different contexts: In normal record edit view, in which case the redirect won't have any effect.
|
||||
// Or in history view, in which case a revert causes the CMS to re-load the edit view.
|
||||
// The X-Pjax header forces a "full" content refresh on redirect.
|
||||
$url = $record->getCMSEditLink();
|
||||
@ -2142,11 +2251,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
$forms[$urlSegment] = $formHtml;
|
||||
}
|
||||
}
|
||||
$pageHtml = '';
|
||||
$recordHtml = '';
|
||||
foreach ($forms as $urlSegment => $html) {
|
||||
$pageHtml .= '<div class="params" id="BatchActionParameters_' . $urlSegment . '" style="display:none">' . $html . '</div>';
|
||||
$recordHtml .= '<div class="params" id="BatchActionParameters_' . $urlSegment . '" style="display:none">' . $html . '</div>';
|
||||
}
|
||||
return new LiteralField('BatchActionParameters', '<div id="BatchActionParameters" class="action-parameters" style="display:none">' . $pageHtml . '</div>');
|
||||
return new LiteralField('BatchActionParameters', '<div id="BatchActionParameters" class="action-parameters" style="display:none">' . $recordHtml . '</div>');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2158,7 +2267,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a completely deleted page from the SiteTree_versions table.
|
||||
* Restore a completely deleted record from the *_versions table.
|
||||
*/
|
||||
public function restore(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
@ -2166,21 +2275,29 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
return new HTTPResponse("Please pass an ID in the form content", 400);
|
||||
}
|
||||
|
||||
$id = (int)$data['ID'];
|
||||
$restoredPage = Versioned::get_latest_version(SiteTree::class, $id);
|
||||
if (!$restoredPage) {
|
||||
return new HTTPResponse("SiteTree #$id not found", 400);
|
||||
$modelClass = $this->getModelClass();
|
||||
if (!$modelClass::has_extension(Versioned::class)) {
|
||||
throw new HTTPResponse_Exception("$modelClass record cannot be restored", 400);
|
||||
}
|
||||
|
||||
$restoredPage = $restoredPage->doRestoreToStage();
|
||||
$id = (int)$data['ID'];
|
||||
$restoredRecord = Versioned::get_latest_version($modelClass, $id);
|
||||
if (!$restoredRecord) {
|
||||
return new HTTPResponse("Record #$id not found", 400);
|
||||
}
|
||||
|
||||
$restoredRecord = $restoredRecord->doRestoreToStage();
|
||||
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(
|
||||
__CLASS__ . '.RESTORED',
|
||||
"Restored '{title}' successfully",
|
||||
['title' => $restoredPage->Title]
|
||||
) ?? '')
|
||||
LeftAndMain::class . '.RESTORED_RECORD',
|
||||
'Restored {name} "{title}"',
|
||||
[
|
||||
'name' => $restoredRecord->i18n_singular_name(),
|
||||
'title' => $restoredRecord->Title,
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
return $this->getResponseNegotiator()->respond($this->getRequest());
|
||||
@ -2194,31 +2311,34 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
if (($id = $this->urlParams['ID']) && is_numeric($id)) {
|
||||
$page = SiteTree::get()->byID($id);
|
||||
if ($page && !$page->canCreate(null, ['Parent' => $page->Parent()])) {
|
||||
$record = DataObject::get($this->getModelClass())->byID($id);
|
||||
if ($record && !$record->canCreate(null, ['Parent' => $record->Parent()])) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
if (!$page || !$page->ID) {
|
||||
if (!$record || !$record->ID) {
|
||||
throw new HTTPResponse_Exception("Bad record ID #$id", 404);
|
||||
}
|
||||
|
||||
$newPage = $page->duplicate();
|
||||
$newRecord = $record->duplicate();
|
||||
|
||||
// ParentID can be hard-set in the URL. This is useful for pages with multiple parents
|
||||
if (isset($_GET['parentID']) && is_numeric($_GET['parentID'])) {
|
||||
$newPage->ParentID = $_GET['parentID'];
|
||||
$newPage->write();
|
||||
$newRecord->ParentID = $_GET['parentID'];
|
||||
$newRecord->write();
|
||||
}
|
||||
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(
|
||||
__CLASS__ . '.DUPLICATED',
|
||||
"Duplicated '{title}' successfully",
|
||||
['title' => $newPage->Title]
|
||||
) ?? '')
|
||||
LeftAndMain::class . '.DUPLICATED_RECORD',
|
||||
'Duplicated {name} "{title}"',
|
||||
[
|
||||
'name' => $newRecord->i18n_singular_name(),
|
||||
'title' => $newRecord->Title,
|
||||
]
|
||||
))
|
||||
);
|
||||
$url = $newPage->getCMSEditLink();
|
||||
$url = $newRecord->getCMSEditLink();
|
||||
$this->getResponse()->addHeader('X-ControllerURL', $url);
|
||||
$this->getRequest()->addHeader('X-Pjax', 'Content');
|
||||
$this->getResponse()->addHeader('X-Pjax', 'Content');
|
||||
@ -2236,25 +2356,28 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
Environment::increaseTimeLimitTo();
|
||||
if (($id = $this->urlParams['ID']) && is_numeric($id)) {
|
||||
$page = SiteTree::get()->byID($id);
|
||||
if ($page && !$page->canCreate(null, ['Parent' => $page->Parent()])) {
|
||||
$record = DataObject::get($this->getModelClass())->byID($id);
|
||||
if ($record && !$record->canCreate(null, ['Parent' => $record->Parent()])) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
if (!$page || !$page->ID) {
|
||||
if (!$record || !$record->ID) {
|
||||
throw new HTTPResponse_Exception("Bad record ID #$id", 404);
|
||||
}
|
||||
|
||||
$newPage = $page->duplicateWithChildren();
|
||||
$newRecord = $record->duplicateWithChildren();
|
||||
|
||||
$this->getResponse()->addHeader(
|
||||
'X-Status',
|
||||
rawurlencode(_t(
|
||||
__CLASS__ . '.DUPLICATEDWITHCHILDREN',
|
||||
"Duplicated '{title}' and children successfully",
|
||||
['title' => $newPage->Title]
|
||||
LeftAndMain::class . '.DUPLICATED_RECORD_WITH_CHILDREN',
|
||||
'Duplicated {name} "{title}" and children',
|
||||
[
|
||||
'name' => $newRecord->i18n_singular_name(),
|
||||
'title' => $newRecord->Title,
|
||||
]
|
||||
) ?? '')
|
||||
);
|
||||
$url = $newPage->getCMSEditLink();
|
||||
$url = $newRecord->getCMSEditLink();
|
||||
$this->getResponse()->addHeader('X-ControllerURL', $url);
|
||||
$this->getRequest()->addHeader('X-Pjax', 'Content');
|
||||
$this->getResponse()->addHeader('X-Pjax', 'Content');
|
||||
@ -2266,11 +2389,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
|
||||
public function providePermissions()
|
||||
{
|
||||
$title = CMSPagesController::menu_title();
|
||||
$title = CMSMain::menu_title();
|
||||
return [
|
||||
"CMS_ACCESS_CMSMain" => [
|
||||
'name' => _t(__CLASS__ . '.ACCESS', "Access to '{title}' section", ['title' => $title]),
|
||||
'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
|
||||
'name' => _t(LeftAndMain::class . '.ACCESS', "Access to '{title}' section", ['title' => $title]),
|
||||
'category' => _t(LeftAndMain::class . '.CMS_ACCESS_CATEGORY', 'CMS Access'),
|
||||
'help' => _t(
|
||||
__CLASS__ . '.ACCESS_HELP',
|
||||
'Allow viewing of the section containing page tree and content. View and edit permissions can be handled through page specific dropdowns, as well as the separate "Content permissions".'
|
||||
@ -2285,7 +2408,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCMSTreeTitle()
|
||||
public function getCMSTreeTitle()
|
||||
{
|
||||
$rootTitle = SiteConfig::current_site_config()->Title;
|
||||
$this->extend('updateCMSTreeTitle', $rootTitle);
|
||||
@ -2293,7 +2416,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache key for SiteTreeHints() method
|
||||
* Cache key for TreeHints() method
|
||||
*
|
||||
* @param $memberID
|
||||
* @return string
|
||||
@ -2301,9 +2424,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
protected function generateHintsCacheKey($memberID)
|
||||
{
|
||||
$baseKey = $memberID . '_' . __CLASS__;
|
||||
|
||||
$this->extend('updateHintsCacheKey', $baseKey);
|
||||
|
||||
return md5($baseKey ?? '');
|
||||
}
|
||||
|
||||
@ -2322,16 +2443,28 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
|
||||
*/
|
||||
public function flushMemberCache($memberIDs = null)
|
||||
{
|
||||
$cache = $this->getHintsCache();
|
||||
$hintsCache = $this->getHintsCache();
|
||||
$childrenCache = $this->getCreatableChildrenCache();
|
||||
|
||||
if (!$memberIDs) {
|
||||
$cache->clear();
|
||||
$hintsCache->clear();
|
||||
$childrenCache->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($memberIDs as $memberID) {
|
||||
$key = $this->generateHintsCacheKey($memberID);
|
||||
$cache->delete($key);
|
||||
$hintsKey = $this->generateHintsCacheKey($memberID);
|
||||
$hintsCache->delete($hintsKey);
|
||||
$childrenKey = $this->generateChildrenCacheKey($memberID);
|
||||
$childrenCache->delete($childrenKey);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateChildrenCacheKey(?int $memberID = null)
|
||||
{
|
||||
if ($memberID === null) {
|
||||
$memberID = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
|
||||
}
|
||||
return md5($memberID . '_' . __CLASS__);
|
||||
}
|
||||
}
|
||||
|
@ -1,239 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Controllers;
|
||||
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
use SilverStripe\Forms\LiteralField;
|
||||
use SilverStripe\Forms\OptionsetField;
|
||||
use SilverStripe\Forms\SelectionGroup;
|
||||
use SilverStripe\Forms\SelectionGroup_Item;
|
||||
use SilverStripe\Forms\TreeDropdownField;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\SiteConfig\SiteConfig;
|
||||
|
||||
class CMSPageAddController extends CMSPageEditController
|
||||
{
|
||||
|
||||
private static $url_segment = 'pages/add';
|
||||
private static $url_rule = '/$Action/$ID/$OtherID';
|
||||
private static $url_priority = 42;
|
||||
private static $menu_title = 'Add page';
|
||||
private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
|
||||
|
||||
private static $allowed_actions = [
|
||||
'AddForm',
|
||||
'doAdd',
|
||||
'doCancel'
|
||||
];
|
||||
|
||||
/**
|
||||
* @return Form
|
||||
*/
|
||||
public function AddForm()
|
||||
{
|
||||
$pageTypes = [];
|
||||
$defaultIcon = Config::inst()->get(SiteTree::class, 'icon_class');
|
||||
|
||||
foreach ($this->PageTypes() as $type) {
|
||||
$class = $type->getField('ClassName');
|
||||
$icon = Config::inst()->get($class, 'icon_class') ?: $defaultIcon;
|
||||
|
||||
// If the icon is the SiteTree default and there's some specific icon being provided by `getPageIconURL`
|
||||
// then we don't need to add the icon class. Otherwise the class take precedence.
|
||||
if ($icon === $defaultIcon && !empty(singleton($class)->getPageIconURL())) {
|
||||
$icon = '';
|
||||
}
|
||||
|
||||
$html = sprintf(
|
||||
'<span class="page-icon %s class-%s"></span><span class="title">%s</span><span class="form__field-description">%s</span>',
|
||||
$icon,
|
||||
Convert::raw2htmlid($class),
|
||||
$type->getField('AddAction'),
|
||||
$type->getField('Description')
|
||||
);
|
||||
$pageTypes[$class] = DBField::create_field('HTMLFragment', $html);
|
||||
}
|
||||
// Ensure generic page type shows on top
|
||||
if (isset($pageTypes['Page'])) {
|
||||
$pageTitle = $pageTypes['Page'];
|
||||
$pageTypes = array_merge(['Page' => $pageTitle], $pageTypes);
|
||||
}
|
||||
|
||||
$numericLabelTmpl = '<span class="step-label"><span class="flyout">Step %d. </span><span class="title">%s</span></span>';
|
||||
|
||||
$topTitle = _t('SilverStripe\\CMS\\Controllers\\CMSPageAddController.ParentMode_top', 'Top level');
|
||||
$childTitle = _t('SilverStripe\\CMS\\Controllers\\CMSPageAddController.ParentMode_child', 'Under another page');
|
||||
|
||||
$fields = new FieldList(
|
||||
$parentModeField = new SelectionGroup(
|
||||
"ParentModeField",
|
||||
[
|
||||
$topField = new SelectionGroup_Item(
|
||||
"top",
|
||||
null,
|
||||
$topTitle
|
||||
),
|
||||
new SelectionGroup_Item(
|
||||
'child',
|
||||
$parentField = new TreeDropdownField(
|
||||
"ParentID",
|
||||
"",
|
||||
SiteTree::class,
|
||||
'ID',
|
||||
'TreeTitle'
|
||||
),
|
||||
$childTitle
|
||||
)
|
||||
]
|
||||
),
|
||||
new LiteralField(
|
||||
'RestrictedNote',
|
||||
sprintf(
|
||||
'<p class="alert alert-info message-restricted">%s</p>',
|
||||
_t(
|
||||
'SilverStripe\\CMS\\Controllers\\CMSMain.AddPageRestriction',
|
||||
'Note: Some page types are not allowed for this selection'
|
||||
)
|
||||
)
|
||||
),
|
||||
$typeField = new OptionsetField(
|
||||
"PageType",
|
||||
DBField::create_field(
|
||||
'HTMLFragment',
|
||||
sprintf($numericLabelTmpl ?? '', 2, _t('SilverStripe\\CMS\\Controllers\\CMSMain.ChoosePageType', 'Choose page type'))
|
||||
),
|
||||
$pageTypes,
|
||||
'Page'
|
||||
)
|
||||
);
|
||||
|
||||
$parentModeField->setTitle(DBField::create_field(
|
||||
'HTMLFragment',
|
||||
sprintf($numericLabelTmpl ?? '', 1, _t('SilverStripe\\CMS\\Controllers\\CMSMain.ChoosePageParentMode', 'Choose where to create this page'))
|
||||
));
|
||||
|
||||
$parentField->setSearchFunction(function ($sourceObject, $labelField, $search) {
|
||||
return DataObject::get($sourceObject)
|
||||
->filterAny([
|
||||
'MenuTitle:PartialMatch' => $search,
|
||||
'Title:PartialMatch' => $search,
|
||||
]);
|
||||
});
|
||||
|
||||
$parentModeField->addExtraClass('parent-mode');
|
||||
|
||||
// CMSMain->currentPageID() automatically sets the homepage,
|
||||
// which we need to counteract in the default selection (which should default to root, ID=0)
|
||||
if ($parentID = $this->getRequest()->getVar('ParentID')) {
|
||||
$parentModeField->setValue('child');
|
||||
$parentField->setValue((int)$parentID);
|
||||
} else {
|
||||
$parentModeField->setValue('top');
|
||||
}
|
||||
|
||||
// Check if the current user has enough permissions to create top level pages
|
||||
// If not, then disable the option to do that
|
||||
if (!SiteConfig::current_site_config()->canCreateTopLevel()) {
|
||||
$topField->setDisabled(true);
|
||||
$parentModeField->setValue('child');
|
||||
}
|
||||
|
||||
$actions = new FieldList(
|
||||
FormAction::create("doAdd", _t('SilverStripe\\CMS\\Controllers\\CMSMain.Create', "Create"))
|
||||
->addExtraClass('btn-primary font-icon-plus-circled')
|
||||
->setUseButtonTag(true),
|
||||
FormAction::create("doCancel", _t('SilverStripe\\CMS\\Controllers\\CMSMain.Cancel', "Cancel"))
|
||||
->addExtraClass('btn-secondary')
|
||||
->setUseButtonTag(true)
|
||||
);
|
||||
|
||||
$this->extend('updatePageOptions', $fields);
|
||||
|
||||
$negotiator = $this->getResponseNegotiator();
|
||||
$form = Form::create(
|
||||
$this,
|
||||
"AddForm",
|
||||
$fields,
|
||||
$actions
|
||||
)->setHTMLID('Form_AddForm')->setStrictFormMethodCheck(false);
|
||||
$form->setAttribute('data-hints', $this->SiteTreeHints());
|
||||
$form->setAttribute('data-childfilter', $this->Link('childfilter'));
|
||||
$form->setValidationResponseCallback(function (ValidationResult $errors) use ($negotiator, $form) {
|
||||
$request = $this->getRequest();
|
||||
if ($request->isAjax() && $negotiator) {
|
||||
$result = $form->forTemplate();
|
||||
return $negotiator->respond($request, [
|
||||
'CurrentForm' => function () use ($result) {
|
||||
return $result;
|
||||
}
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$form->addExtraClass('flexbox-area-grow fill-height cms-add-form cms-content cms-edit-form ' . $this->BaseCSSClasses());
|
||||
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function doAdd(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$className = isset($data['PageType']) ? $data['PageType'] : "Page";
|
||||
$parentID = isset($data['ParentID']) ? (int)$data['ParentID'] : 0;
|
||||
|
||||
if (!$parentID && isset($data['Parent'])) {
|
||||
$page = SiteTree::get_by_link($data['Parent']);
|
||||
if ($page) {
|
||||
$parentID = $page->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_numeric($parentID) && $parentID > 0) {
|
||||
$parentObj = SiteTree::get()->byID($parentID);
|
||||
} else {
|
||||
$parentObj = null;
|
||||
}
|
||||
|
||||
if (!$parentObj || !$parentObj->ID) {
|
||||
$parentID = 0;
|
||||
}
|
||||
|
||||
if (!singleton($className)->canCreate(Security::getCurrentUser(), ['Parent' => $parentObj])) {
|
||||
return Security::permissionFailure($this);
|
||||
}
|
||||
|
||||
$record = $this->getNewItem("new-$className-$parentID", false);
|
||||
$this->extend('updateDoAdd', $record, $form);
|
||||
$record->write();
|
||||
|
||||
$editController = CMSPageEditController::singleton();
|
||||
$editController->setRequest($this->getRequest());
|
||||
$editController->setCurrentPageID($record->ID);
|
||||
|
||||
$session = $this->getRequest()->getSession();
|
||||
$session->set(
|
||||
"FormInfo.Form_EditForm.formError.message",
|
||||
_t('SilverStripe\\CMS\\Controllers\\CMSMain.PageAdded', 'Successfully created page')
|
||||
);
|
||||
$session->set("FormInfo.Form_EditForm.formError.type", 'good');
|
||||
|
||||
return $this->redirect(Controller::join_links($editController->Link('show'), $record->ID));
|
||||
}
|
||||
|
||||
public function doCancel(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
return $this->redirect(CMSMain::singleton()->Link());
|
||||
}
|
||||
}
|
@ -2,17 +2,15 @@
|
||||
|
||||
namespace SilverStripe\CMS\Controllers;
|
||||
|
||||
use Page;
|
||||
use SilverStripe\Admin\LeftAndMain;
|
||||
use SilverStripe\CampaignAdmin\AddToCampaignHandler;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Core\ArrayLib;
|
||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* @package cms
|
||||
@ -57,7 +55,7 @@ class CMSPageEditController extends CMSMain
|
||||
public function addtocampaign(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$id = $data['ID'];
|
||||
$record = \Page::get()->byID($id);
|
||||
$record = DataObject::get($this->getModelClass())->byID($id);
|
||||
|
||||
$handler = AddToCampaignHandler::create($this, $record);
|
||||
$response = $handler->addToCampaign($record, $data);
|
||||
@ -95,15 +93,16 @@ class CMSPageEditController extends CMSMain
|
||||
*/
|
||||
public function getAddToCampaignForm($id)
|
||||
{
|
||||
$modelClass = $this->getModelClass();
|
||||
// Get record-specific fields
|
||||
$record = SiteTree::get()->byID($id);
|
||||
$record = DataObject::get($modelClass)->byID($id);
|
||||
|
||||
if (!$record) {
|
||||
$this->httpError(404, _t(
|
||||
__CLASS__ . '.ErrorNotFound',
|
||||
'That {Type} couldn\'t be found',
|
||||
'',
|
||||
['Type' => Page::singleton()->i18n_singular_name()]
|
||||
['Type' => DataObject::singleton($modelClass)->i18n_singular_name()]
|
||||
));
|
||||
return null;
|
||||
}
|
||||
@ -112,7 +111,7 @@ class CMSPageEditController extends CMSMain
|
||||
__CLASS__.'.ErrorItemPermissionDenied',
|
||||
'It seems you don\'t have the necessary permissions to add {ObjectTitle} to a campaign',
|
||||
'',
|
||||
['ObjectTitle' => Page::singleton()->i18n_singular_name()]
|
||||
['ObjectTitle' => DataObject::singleton($modelClass)->i18n_singular_name()]
|
||||
));
|
||||
return null;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\CMS\Controllers;
|
||||
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Model\ArrayData;
|
||||
|
||||
class CMSPageSettingsController extends CMSMain
|
||||
@ -15,11 +16,19 @@ class CMSPageSettingsController extends CMSMain
|
||||
|
||||
private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
|
||||
|
||||
public function getEditForm($id = null, $fields = null)
|
||||
public function getEditForm($id = null, $fields = null): Form
|
||||
{
|
||||
$record = $this->getRecord($id ?: $this->currentPageID());
|
||||
$record = $this->getRecord($id ?: $this->currentRecordID());
|
||||
|
||||
return parent::getEditForm($id, ($record) ? $record->getSettingsFields() : null);
|
||||
// @TODO ideally settings isn't its own special thing...
|
||||
// can we refactor this so it's just another tab in the main form? And just have it lazyload or something?
|
||||
// At the very least this tab must NOT appear if there are no fields for it.
|
||||
if ($record && $record->hasMethod('getSettingsFields')) {
|
||||
$fields = $record->getSettingsFields();
|
||||
} else {
|
||||
$fields = null;
|
||||
}
|
||||
return parent::getEditForm($id, $fields);
|
||||
}
|
||||
|
||||
public function getTabIdentifier()
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Controllers;
|
||||
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Model\List\ArrayList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Model\ArrayData;
|
||||
use stdClass;
|
||||
|
||||
class CMSPagesController extends CMSMain
|
||||
{
|
||||
|
||||
private static $url_segment = 'pages';
|
||||
|
||||
private static $url_rule = '/$Action/$ID/$OtherID';
|
||||
|
||||
private static $url_priority = 40;
|
||||
|
||||
private static $menu_title = 'Pages';
|
||||
|
||||
private static $required_permission_codes = 'CMS_ACCESS_CMSMain';
|
||||
|
||||
public function LinkPreview()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isCurrentPage(DataObject $record)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -113,7 +113,7 @@ abstract class CMSSiteTreeFilter implements LeftAndMain_SearchFilter
|
||||
return $this->numChildrenMethod;
|
||||
}
|
||||
|
||||
public function getPageClasses($page)
|
||||
public function getRecordClasses($page)
|
||||
{
|
||||
if ($this->_cache_ids === null) {
|
||||
$this->populateIDs();
|
||||
@ -130,7 +130,7 @@ abstract class CMSSiteTreeFilter implements LeftAndMain_SearchFilter
|
||||
/**
|
||||
* Gets the list of filtered pages
|
||||
*
|
||||
* @see {@link SiteTree::getStatusFlags()}
|
||||
* @see {@link ModelData::getStatusFlags()}
|
||||
* @return SS_List
|
||||
*/
|
||||
abstract public function getFilteredPages();
|
||||
@ -178,7 +178,7 @@ abstract class CMSSiteTreeFilter implements LeftAndMain_SearchFilter
|
||||
}
|
||||
}
|
||||
|
||||
public function isPageIncluded($page)
|
||||
public function isRecordIncluded($page)
|
||||
{
|
||||
if ($this->_cache_ids === null) {
|
||||
$this->populateIDs();
|
||||
|
@ -36,7 +36,7 @@ class CMSSiteTreeFilter_PublishedPages extends CMSSiteTreeFilter
|
||||
/**
|
||||
* Filters out all pages who's status who's status that doesn't exist on live
|
||||
*
|
||||
* @see {@link SiteTree::getStatusFlags()}
|
||||
* @see {@link ModelData::getStatusFlags()}
|
||||
* @return SS_List
|
||||
*/
|
||||
public function getFilteredPages()
|
||||
|
@ -30,7 +30,7 @@ class CMSSiteTreeFilter_StatusDeletedPages extends CMSSiteTreeFilter
|
||||
/**
|
||||
* Filters out all pages who's status is set to "Deleted".
|
||||
*
|
||||
* @see {@link SiteTree::getStatusFlags()}
|
||||
* @see {@link ModelData::getStatusFlags()}
|
||||
* @return SS_List
|
||||
*/
|
||||
public function getFilteredPages()
|
||||
|
@ -20,7 +20,7 @@ class CMSSiteTreeFilter_StatusDraftPages extends CMSSiteTreeFilter
|
||||
/**
|
||||
* Filters out all pages who's status is set to "Draft".
|
||||
*
|
||||
* @see {@link SiteTree::getStatusFlags()}
|
||||
* @see {@link ModelData::getStatusFlags()}
|
||||
* @return SS_List
|
||||
*/
|
||||
public function getFilteredPages()
|
||||
|
@ -18,6 +18,8 @@ use SilverStripe\View\Requirements;
|
||||
/**
|
||||
* Extension to include custom page icons
|
||||
*
|
||||
* @TODO AAAHHHHHHHHHHH
|
||||
*
|
||||
* @extends Extension<LeftAndMain>
|
||||
*/
|
||||
class LeftAndMainPageIconsExtension extends Extension implements Flushable
|
||||
@ -60,13 +62,13 @@ class LeftAndMainPageIconsExtension extends Extension implements Flushable
|
||||
$css = '';
|
||||
$classes = ClassInfo::subclassesFor(SiteTree::class);
|
||||
foreach ($classes as $class) {
|
||||
if (!empty(Config::inst()->get($class, 'icon_class', Config::UNINHERITED))) {
|
||||
if (!empty(Config::inst()->get($class, 'cms_icon_class', Config::UNINHERITED))) {
|
||||
continue;
|
||||
}
|
||||
$iconURL = SiteTree::singleton($class)->getPageIconURL();
|
||||
$iconURL = CMSMain::singleton()->getRecordIconUrl(SiteTree::class);
|
||||
if ($iconURL) {
|
||||
$cssClass = Convert::raw2htmlid($class);
|
||||
$selector = sprintf('.page-icon.class-%1$s, li.class-%1$s > a .jstree-pageicon', $cssClass);
|
||||
$selector = sprintf('.record-icon.class-%1$s, li.class-%1$s > a .jstree-recordicon', $cssClass);
|
||||
$css .= sprintf('%s { background: transparent url(\'%s\') 0 0 no-repeat; }', $selector, $iconURL);
|
||||
}
|
||||
}
|
||||
|
230
code/Forms/CMSMainAddForm.php
Normal file
230
code/Forms/CMSMainAddForm.php
Normal file
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Forms;
|
||||
|
||||
use SilverStripe\Admin\LeftAndMain;
|
||||
use SilverStripe\CMS\Controllers\CMSMain;
|
||||
use SilverStripe\CMS\Controllers\CMSPageEditController;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
use SilverStripe\Forms\LiteralField;
|
||||
use SilverStripe\Forms\OptionsetField;
|
||||
use SilverStripe\Forms\SelectionGroup;
|
||||
use SilverStripe\Forms\SelectionGroup_Item;
|
||||
use SilverStripe\Forms\TreeDropdownField;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\SiteConfig\SiteConfig;
|
||||
|
||||
class CMSMainAddForm extends Form
|
||||
{
|
||||
public function __construct(CMSMain $controller)
|
||||
{
|
||||
$modelClass = $controller->getModelClass();
|
||||
$singleton = DataObject::singleton($modelClass);
|
||||
$recordTypes = [];
|
||||
$defaultIcon = $controller->getRecordIconCssClass($singleton);
|
||||
$defaultRecordType = 'Page'; // @TODO GENERICALIFICATE THIS! Note it's not necessarily the same as $modelClass, 'cause SiteTree aint Page
|
||||
|
||||
foreach ($controller->RecordTypes() as $type) {
|
||||
$class = $type->getField('ClassName');
|
||||
$typeSingleton = DataObject::singleton($class);
|
||||
$icon = $controller->getRecordIconCssClass($typeSingleton) ?: $defaultIcon;
|
||||
|
||||
// If the icon is the default and there's some specific icon being provided by `getPageIconURL`
|
||||
// then we don't need to add the icon class. Otherwise the class take precedence.
|
||||
if ($icon === $defaultIcon && !empty($controller->getRecordIconUrl($typeSingleton))) {
|
||||
$icon = '';
|
||||
}
|
||||
|
||||
$html = sprintf(
|
||||
'<span class="record-icon %s class-%s"></span><span class="title">%s</span><span class="form__field-description">%s</span>',
|
||||
$icon,
|
||||
Convert::raw2htmlid($class),
|
||||
$type->getField('AddAction'),
|
||||
$type->getField('Description')
|
||||
);
|
||||
$recordTypes[$class] = DBField::create_field('HTMLFragment', $html);
|
||||
}
|
||||
// Ensure default record type shows on top
|
||||
if (isset($recordTypes[$defaultRecordType])) {
|
||||
$typeName = $recordTypes[$defaultRecordType];
|
||||
$recordTypes = array_merge([$defaultRecordType => $typeName], $recordTypes);
|
||||
}
|
||||
|
||||
$numericLabelTmpl = '<span class="step-label"><span class="flyout">Step %d. </span><span class="title">%s</span></span>';
|
||||
|
||||
$topTitle = _t(__CLASS__ . '.ParentMode_top', 'Top level');
|
||||
$childTitle = _t(
|
||||
__CLASS__ . '.ParentMode_child',
|
||||
'Under another {type}',
|
||||
['type' => mb_strtolower($singleton->i18n_singular_name())]
|
||||
);
|
||||
|
||||
$fields = FieldList::create(
|
||||
$parentModeField = SelectionGroup::create(
|
||||
'ParentModeField',
|
||||
[
|
||||
$topField = SelectionGroup_Item::create(
|
||||
'top',
|
||||
null,
|
||||
$topTitle
|
||||
),
|
||||
SelectionGroup_Item::create(
|
||||
'child',
|
||||
$parentField = TreeDropdownField::create(
|
||||
'ParentID',
|
||||
'',
|
||||
$modelClass
|
||||
),
|
||||
$childTitle
|
||||
)
|
||||
]
|
||||
),
|
||||
LiteralField::create(
|
||||
'RestrictedNote',
|
||||
sprintf(
|
||||
'<p class="alert alert-info message-restricted">%s</p>',
|
||||
_t(
|
||||
__CLASS__ . '.AddRecordRestriction',
|
||||
'Note: Some {model} types are not allowed for this selection',
|
||||
['model' => mb_strtolower($singleton->i18n_singular_name())]
|
||||
)
|
||||
)
|
||||
),
|
||||
OptionsetField::create(
|
||||
'RecordType',
|
||||
DBField::create_field(
|
||||
'HTMLFragment',
|
||||
sprintf($numericLabelTmpl, 2, _t(
|
||||
__CLASS__ . '.ChooseRecordType',
|
||||
'Choose {model} type',
|
||||
['model' => mb_strtolower($singleton->i18n_singular_name())]
|
||||
))
|
||||
),
|
||||
$recordTypes,
|
||||
$defaultRecordType
|
||||
)
|
||||
);
|
||||
|
||||
$parentModeField->setTitle(DBField::create_field(
|
||||
'HTMLFragment',
|
||||
sprintf($numericLabelTmpl, 1, _t(__CLASS__ . '.ChooseParentMode', 'Choose where to create this record'))
|
||||
));
|
||||
|
||||
$parentField->setSearchFunction(function ($sourceObject, $labelField, $search) {
|
||||
return DataObject::get($sourceObject)
|
||||
->filterAny([
|
||||
'MenuTitle:PartialMatch' => $search,
|
||||
'Title:PartialMatch' => $search,
|
||||
]);
|
||||
});
|
||||
|
||||
$parentModeField->addExtraClass('parent-mode');
|
||||
|
||||
// CMSMain->currentRecordID() automatically sets the homepage, // @TODO find out what this is about
|
||||
// which we need to counteract in the default selection (which should default to root, ID=0)
|
||||
if ($parentID = $controller->getRequest()->getVar('ParentID')) {
|
||||
$parentModeField->setValue('child');
|
||||
$parentField->setValue((int)$parentID);
|
||||
} else {
|
||||
$parentModeField->setValue('top');
|
||||
}
|
||||
|
||||
// Check if the current user has enough permissions to create top level records
|
||||
// If not, then disable the option to do that
|
||||
if (is_a($modelClass, SiteTree::class, true) && !SiteConfig::current_site_config()->canCreateTopLevel()) { // @TODO probably need to make this generic
|
||||
$topField->setDisabled(true);
|
||||
$parentModeField->setValue('child');
|
||||
}
|
||||
|
||||
$actions = FieldList::create(
|
||||
FormAction::create('doAdd', _t(CMSMain::class . '.Create', 'Create'))
|
||||
->addExtraClass('btn-primary font-icon-plus-circled')
|
||||
->setUseButtonTag(true),
|
||||
FormAction::create('doCancel', _t(CMSMain::class . '.Cancel', 'Cancel'))
|
||||
->addExtraClass('btn-secondary')
|
||||
->setUseButtonTag(true)
|
||||
);
|
||||
|
||||
$this->extend('updateFields', $fields);
|
||||
parent::__construct($controller, 'AddForm', $fields, $actions);
|
||||
|
||||
$negotiator = $controller->getResponseNegotiator();
|
||||
$this->setHTMLID('Form_AddForm')->setStrictFormMethodCheck(false);
|
||||
$this->setAttribute('data-hints', $controller->TreeHints());
|
||||
$this->setAttribute('data-childfilter', $controller->Link('childfilter'));
|
||||
$this->setValidationResponseCallback(function () use ($negotiator, $controller) {
|
||||
$request = $controller->getRequest();
|
||||
if ($request->isAjax() && $negotiator) {
|
||||
$result = $this->forTemplate();
|
||||
return $negotiator->respond($request, [
|
||||
'CurrentForm' => function () use ($result) {
|
||||
return $result;
|
||||
}
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$this->addExtraClass('flexbox-area-grow fill-height cms-add-form cms-content cms-edit-form ' . $controller->BaseCSSClasses());
|
||||
$this->setTemplate($controller->getTemplatesWithSuffix('_AddForm'));
|
||||
}
|
||||
|
||||
public function doAdd(array $data, Form $form): HTTPResponse
|
||||
{
|
||||
$defaultRecordType = 'Page'; // @TODO GENERICALIFICATE THIS
|
||||
$controller = $this->getController();
|
||||
$modelClass = $controller->getModelClass();
|
||||
$className = isset($data['RecordType']) ? $data['RecordType'] : $defaultRecordType; // @TODO shouldn't this throw an error??
|
||||
$parentID = isset($data['ParentID']) ? (int)$data['ParentID'] : 0;
|
||||
|
||||
if (!$parentID && isset($data['Parent'])) {
|
||||
$parentRecord = $modelClass::get_by_link($data['Parent']); // @TODO Obviously no good
|
||||
if ($parentRecord) {
|
||||
$parentID = $parentRecord->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_numeric($parentID) && $parentID > 0) {
|
||||
$parentObj = DataObject::get($modelClass)->byID($parentID);
|
||||
} else {
|
||||
$parentObj = null;
|
||||
}
|
||||
|
||||
if (!$parentObj || !$parentObj->ID) {
|
||||
$parentID = 0;
|
||||
}
|
||||
|
||||
if (!DataObject::singleton($className)->canCreate(Security::getCurrentUser(), ['Parent' => $parentObj])) {
|
||||
return Security::permissionFailure($controller);
|
||||
}
|
||||
|
||||
$record = $controller->getNewItem("new-$className-$parentID", false);
|
||||
$controller->extend('updateDoAdd', $record, $form);
|
||||
$record->write();
|
||||
|
||||
$editController = CMSPageEditController::singleton();
|
||||
$editController->setRequest($controller->getRequest());
|
||||
$editController->setCurrentRecordID($record->ID);
|
||||
|
||||
$controller->getResponse()->addHeader('X-Status', rawurlencode(_t(
|
||||
LeftAndMain::class . '.CREATED_RECORD',
|
||||
'Created {name} "{title}"',
|
||||
[
|
||||
'name' => $record->i18n_singular_name(),
|
||||
'title' => $record->Title,
|
||||
]
|
||||
)));
|
||||
return $controller->redirect($editController->Link('show/' . $record->ID));
|
||||
}
|
||||
|
||||
public function doCancel(): HTTPResponse
|
||||
{
|
||||
return $this->getController()->redirect(CMSMain::singleton()->Link()); // @TODO when there's no CMSPageEditController anymore, change this to $this->getController()->Link()
|
||||
}
|
||||
}
|
@ -28,9 +28,7 @@ class InternalLinkFormFactory extends LinkFormFactory
|
||||
TreeDropdownField::create(
|
||||
'PageID',
|
||||
_t(__CLASS__.'.SELECT_PAGE', 'Select a page'),
|
||||
SiteTree::class,
|
||||
'ID',
|
||||
'TreeTitle'
|
||||
SiteTree::class
|
||||
)
|
||||
->setTitleField('MenuTitle')
|
||||
->setHasEmptyDefault(true),
|
||||
|
@ -7,19 +7,19 @@ use SilverStripe\ORM\DataObject;
|
||||
/**
|
||||
* This interface lets us set up objects that will tell us what the current page is.
|
||||
*/
|
||||
interface CurrentPageIdentifier
|
||||
interface CurrentRecordIdentifier
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the current page ID.
|
||||
* @return int
|
||||
*/
|
||||
public function currentPageID();
|
||||
public function currentRecordID();
|
||||
|
||||
/**
|
||||
* Check if the given DataObject is the current page.
|
||||
* @param DataObject $page The page to check.
|
||||
* @return boolean
|
||||
*/
|
||||
public function isCurrentPage(DataObject $page);
|
||||
public function isCurrentRecord(DataObject $page);
|
||||
}
|
@ -23,7 +23,7 @@ class RedirectorPage extends Page
|
||||
{
|
||||
private static $description = 'Redirects requests to another location';
|
||||
|
||||
private static $icon_class = 'font-icon-p-redirect';
|
||||
private static $cms_icon_class = 'font-icon-p-redirect';
|
||||
|
||||
private static $show_stage_link = false;
|
||||
|
||||
|
@ -7,7 +7,6 @@ use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Admin\CMSEditLinkExtension;
|
||||
use SilverStripe\Assets\Shortcodes\FileLinkTracking;
|
||||
use SilverStripe\CMS\Controllers\CMSMain;
|
||||
use SilverStripe\CMS\Controllers\CMSPageEditController;
|
||||
use SilverStripe\CMS\Controllers\ContentController;
|
||||
use SilverStripe\CMS\Controllers\ModelAsController;
|
||||
use SilverStripe\CMS\Controllers\RootURLController;
|
||||
@ -15,14 +14,11 @@ use SilverStripe\CMS\Forms\SiteTreeURLSegmentField;
|
||||
use SilverStripe\Control\ContentNegotiator;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Cache\MemberCacheFlusher;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Flushable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ModuleResource;
|
||||
use SilverStripe\Core\Manifest\ModuleResourceLoader;
|
||||
use SilverStripe\Core\Manifest\VersionProvider;
|
||||
use SilverStripe\Core\Resettable;
|
||||
use SilverStripe\Forms\CheckboxField;
|
||||
@ -35,19 +31,15 @@ use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\GridField\GridField;
|
||||
use SilverStripe\Forms\GridField\GridFieldDataColumns;
|
||||
use SilverStripe\Forms\GridField\GridFieldLazyLoader;
|
||||
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
|
||||
use SilverStripe\Forms\ListboxField;
|
||||
use SilverStripe\Forms\LiteralField;
|
||||
use SilverStripe\Forms\OptionsetField;
|
||||
use SilverStripe\Forms\SearchableMultiDropdownField;
|
||||
use SilverStripe\Forms\Tab;
|
||||
use SilverStripe\Forms\TabSet;
|
||||
use SilverStripe\Forms\TextareaField;
|
||||
use SilverStripe\Forms\TextField;
|
||||
use SilverStripe\Forms\ToggleCompositeField;
|
||||
use SilverStripe\Forms\TreeDropdownField;
|
||||
use SilverStripe\Forms\TreeMultiselectField;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\i18n\i18nEntityProvider;
|
||||
use SilverStripe\Model\List\ArrayList;
|
||||
use SilverStripe\ORM\CMSPreviewable;
|
||||
@ -57,8 +49,8 @@ use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\HasManyList;
|
||||
use SilverStripe\ORM\HiddenClass;
|
||||
use SilverStripe\ORM\Hierarchy\Hierarchy;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Forms\HiddenField;
|
||||
use SilverStripe\Security\Group;
|
||||
use SilverStripe\Security\InheritedPermissions;
|
||||
use SilverStripe\Security\InheritedPermissionsExtension;
|
||||
@ -112,7 +104,7 @@ use SilverStripe\View\SSViewer;
|
||||
* @mixin InheritedPermissionsExtension
|
||||
* @method HasManyList<SiteTreeLink> BackLinks()
|
||||
*/
|
||||
class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvider, CMSPreviewable, Resettable, Flushable, MemberCacheFlusher
|
||||
class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvider, CMSPreviewable, Resettable, Flushable
|
||||
{
|
||||
/**
|
||||
* Indicates what kind of children this page type can have.
|
||||
@ -285,7 +277,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
'Link' => 'Text',
|
||||
'RelativeLink' => 'Text',
|
||||
'AbsoluteLink' => 'Text',
|
||||
'TreeTitle' => 'HTMLFragment',
|
||||
'MetaTags' => 'HTMLFragment',
|
||||
];
|
||||
|
||||
@ -338,21 +329,29 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
private static $can_create = true;
|
||||
|
||||
/**
|
||||
* Icon to use in the CMS page tree. This should be the full filename, relative to the webroot.
|
||||
* Also supports custom CSS rule contents (applied to the correct selector for the tree UI implementation).
|
||||
* Icon to use in the CMS page tree.
|
||||
*
|
||||
* This should be one of the following:
|
||||
* - the full filename, relative to the webroot
|
||||
* - inline "data:image/" url string
|
||||
* - a themed resource reference
|
||||
* - a module resource reference
|
||||
*
|
||||
* If cms_icon_class has a value, cms_icon is ignored for most purposes.
|
||||
*
|
||||
* @see LeftAndMainPageIconsExtension::generatePageIconsCss()
|
||||
* @config
|
||||
* @var string
|
||||
*/
|
||||
private static $icon = null;
|
||||
private static $cms_icon = null;
|
||||
|
||||
/**
|
||||
* Class attached to page icons in the CMS page tree. Also supports font-icon set.
|
||||
* Overrides cms_icon for most purposes
|
||||
* @config
|
||||
* @var string
|
||||
*/
|
||||
private static $icon_class = 'font-icon-page';
|
||||
private static $cms_icon_class = 'font-icon-page';
|
||||
|
||||
private static $extensions = [
|
||||
Hierarchy::class,
|
||||
@ -410,8 +409,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
private static $show_meta_generator_version = true;
|
||||
|
||||
protected $_cache_statusFlags = null;
|
||||
|
||||
/**
|
||||
* Plural form for SiteTree / Page classes. Not inherited by subclasses.
|
||||
*
|
||||
@ -452,18 +449,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
private static $base_description = 'Generic content page';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $dependencies = [
|
||||
'creatableChildrenCache' => '%$' . CacheInterface::class . '.SiteTree_CreatableChildren'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var CacheInterface
|
||||
*/
|
||||
protected $creatableChildrenCache;
|
||||
|
||||
/**
|
||||
* @var VersionProvider
|
||||
*/
|
||||
@ -579,8 +564,17 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
public static function page_type_classes()
|
||||
{
|
||||
$classes = ClassInfo::getValidSubClasses();
|
||||
SiteTree::singleton()->updateValidSubClasses($classes);
|
||||
return $classes;
|
||||
}
|
||||
|
||||
$baseClassIndex = array_search(SiteTree::class, $classes ?? []);
|
||||
/**
|
||||
* Update a list of classes to exclude page types that should be hidden through {@link SiteTree::$hide_pagetypes}
|
||||
* {@see CMSMain::getValidSubClasses}
|
||||
*/
|
||||
public function updateValidSubClasses(array &$classes): void
|
||||
{
|
||||
$baseClassIndex = array_search(SiteTree::class, $classes);
|
||||
if ($baseClassIndex !== false) {
|
||||
unset($classes[$baseClassIndex]);
|
||||
}
|
||||
@ -601,17 +595,26 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
// If any of the descendents don't want any of the elders to show up, cruelly render the elders surplus to
|
||||
// requirements
|
||||
if ($kill_ancestors) {
|
||||
$kill_ancestors = array_unique($kill_ancestors ?? []);
|
||||
$kill_ancestors = array_unique($kill_ancestors);
|
||||
foreach ($kill_ancestors as $mark) {
|
||||
// unset from $classes
|
||||
$idx = array_search($mark, $classes ?? [], true);
|
||||
$idx = array_search($mark, $classes, true);
|
||||
if ($idx !== false) {
|
||||
unset($classes[$idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
/**
|
||||
* Update CSS classes for the icon used in the CMS site tree
|
||||
* {@see CMSMain::getRecordTreeMarkup}
|
||||
*/
|
||||
public function updateTreeIconClasses(array &$classes): void
|
||||
{
|
||||
if ($this->isHomePage()) {
|
||||
$classes[] = 'homepage';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -783,12 +786,12 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
public function isCurrent()
|
||||
{
|
||||
$currentPage = Director::get_current_page();
|
||||
if ($currentPage instanceof ContentController) {
|
||||
$currentPage = $currentPage->data();
|
||||
$currentRecord = Director::get_current_page();
|
||||
if ($currentRecord instanceof ContentController) {
|
||||
$currentRecord = $currentRecord->data();
|
||||
}
|
||||
if ($currentPage instanceof SiteTree) {
|
||||
return $currentPage === $this || $currentPage->ID === $this->ID;
|
||||
if ($currentRecord instanceof SiteTree) {
|
||||
return $currentRecord === $this || $currentRecord->ID === $this->ID;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1015,25 +1018,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CacheInterface $cache
|
||||
* @return $this
|
||||
*/
|
||||
public function setCreatableChildrenCache(CacheInterface $cache)
|
||||
{
|
||||
$this->creatableChildrenCache = $cache;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CacheInterface $cache
|
||||
*/
|
||||
public function getCreatableChildrenCache()
|
||||
{
|
||||
return $this->creatableChildrenCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string of the form "parent - page" or "grandparent - parent - page" using page titles
|
||||
*
|
||||
@ -1812,32 +1796,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
parent::onAfterDelete();
|
||||
}
|
||||
|
||||
public function flushCache($persistent = true)
|
||||
{
|
||||
parent::flushCache($persistent);
|
||||
$this->_cache_statusFlags = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the member specific cache for creatable children
|
||||
*
|
||||
* @param array $memberIDs
|
||||
*/
|
||||
public function flushMemberCache($memberIDs = null)
|
||||
{
|
||||
$cache = SiteTree::singleton()->getCreatableChildrenCache();
|
||||
|
||||
if (!$memberIDs) {
|
||||
$cache->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($memberIDs as $memberID) {
|
||||
$key = $this->generateChildrenCacheKey($memberID);
|
||||
$cache->delete($key);
|
||||
}
|
||||
}
|
||||
|
||||
public function validate()
|
||||
{
|
||||
$result = parent::validate();
|
||||
@ -2241,6 +2199,14 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
"Title"
|
||||
);
|
||||
}
|
||||
|
||||
// Necessary for updating URLSegment when changing the default page title.
|
||||
// LiveLink is (for better or for worse) used as part of the "is this page new?" detection.
|
||||
$fields->push($liveLinkField = HiddenField::create('LiveLink'));
|
||||
$liveLink = $this->getAbsoluteLiveLink();
|
||||
if ($liveLink) {
|
||||
$liveLinkField->setValue($liveLink);
|
||||
}
|
||||
});
|
||||
|
||||
return parent::getCMSFields();
|
||||
@ -2778,9 +2744,9 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
// Sort alphabetically, and put current on top
|
||||
asort($result);
|
||||
if (isset($result[$this->ClassName])) {
|
||||
$currentPageTypeName = $result[$this->ClassName];
|
||||
$currentRecordTypeName = $result[$this->ClassName];
|
||||
unset($result[$this->ClassName]);
|
||||
$result = [$this->ClassName => $currentPageTypeName] + $result;
|
||||
$result = [$this->ClassName => $currentRecordTypeName] + $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -2834,41 +2800,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
return $allowedChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a list of the page types that can be created under this specific page, including font icons
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function creatableChildPages()
|
||||
{
|
||||
// Build the list of candidate children
|
||||
$cache = SiteTree::singleton()->getCreatableChildrenCache();
|
||||
$cacheKey = $this->generateChildrenCacheKey(Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0);
|
||||
$children = $cache->get($cacheKey, []);
|
||||
|
||||
if (!$children || !isset($children[$this->ID])) {
|
||||
$children[$this->ID] = [];
|
||||
$candidates = static::page_type_classes();
|
||||
|
||||
foreach ($candidates as $childClass) {
|
||||
$child = singleton($childClass);
|
||||
|
||||
if ($child->canCreate(null, ['Parent' => $this])) {
|
||||
$children[$this->ID][] = [
|
||||
'ClassName' => $childClass,
|
||||
'Title' => $child->i18n_singular_name(),
|
||||
'IconClass' => $child->getIconClass(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$cache->set($cacheKey, $children);
|
||||
}
|
||||
|
||||
return $children[$this->ID];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class name of the default class for children of this page.
|
||||
*
|
||||
@ -2927,102 +2858,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flag provides the user with additional data about the current page status, for example a "removed from draft"
|
||||
* status. Each page can have more than one status flag. Returns a map of a unique key to a (localized) title for
|
||||
* the flag. The unique key can be reused as a CSS class. Use the 'updateStatusFlags' extension point to customize
|
||||
* the flags.
|
||||
*
|
||||
* Example (simple):
|
||||
* "deletedonlive" => "Deleted"
|
||||
*
|
||||
* Example (with optional title attribute):
|
||||
* "deletedonlive" => ['text' => "Deleted", 'title' => 'This page has been deleted']
|
||||
*
|
||||
* @param bool $cached Whether to serve the fields from cache; false regenerate them
|
||||
* @return array
|
||||
*/
|
||||
public function getStatusFlags($cached = true)
|
||||
{
|
||||
if (!$this->_cache_statusFlags || !$cached) {
|
||||
$flags = [];
|
||||
if ($this->isOnLiveOnly()) {
|
||||
$flags['removedfromdraft'] = [
|
||||
'text' => _t(__CLASS__.'.ONLIVEONLYSHORT', 'On live only'),
|
||||
'title' => _t(__CLASS__.'.ONLIVEONLYSHORTHELP', 'Page is published, but has been deleted from draft'),
|
||||
];
|
||||
} elseif ($this->isArchived()) {
|
||||
$flags['archived'] = [
|
||||
'text' => _t(__CLASS__.'.ARCHIVEDPAGESHORT', 'Archived'),
|
||||
'title' => _t(__CLASS__.'.ARCHIVEDPAGEHELP', 'Page is removed from draft and live'),
|
||||
];
|
||||
} elseif ($this->isOnDraftOnly()) {
|
||||
$flags['addedtodraft'] = [
|
||||
'text' => _t(__CLASS__.'.ADDEDTODRAFTSHORT', 'Draft'),
|
||||
'title' => _t(__CLASS__.'.ADDEDTODRAFTHELP', "Page has not been published yet")
|
||||
];
|
||||
} elseif ($this->isModifiedOnDraft()) {
|
||||
$flags['modified'] = [
|
||||
'text' => _t(__CLASS__.'.MODIFIEDONDRAFTSHORT', 'Modified'),
|
||||
'title' => _t(__CLASS__.'.MODIFIEDONDRAFTHELP', 'Page has unpublished changes'),
|
||||
];
|
||||
}
|
||||
|
||||
$this->extend('updateStatusFlags', $flags);
|
||||
|
||||
$this->_cache_statusFlags = $flags;
|
||||
}
|
||||
|
||||
return $this->_cache_statusFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSS class used for the page icon in the site tree.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIconClass()
|
||||
{
|
||||
if ($this->config()->get('icon')) {
|
||||
return '';
|
||||
}
|
||||
return $this->config()->get('icon_class');
|
||||
}
|
||||
|
||||
/**
|
||||
* getTreeTitle will return three <span> html DOM elements, an empty <span> with the class 'jstree-pageicon' in
|
||||
* front, following by a <span> wrapping around its MenuTitle, then following by a <span> indicating its
|
||||
* publication status.
|
||||
*
|
||||
* @return string An HTML string ready to be directly used in a template
|
||||
*/
|
||||
public function getTreeTitle()
|
||||
{
|
||||
$children = $this->creatableChildPages();
|
||||
$flags = $this->getStatusFlags();
|
||||
$treeTitle = sprintf(
|
||||
'<span class="jstree-pageicon page-icon %s class-%s%s"></span><span class="item" data-allowedchildren="%s">%s</span>',
|
||||
$this->getIconClass(),
|
||||
Convert::raw2htmlid(static::class),
|
||||
$this->isHomePage() ? ' homepage' : '',
|
||||
Convert::raw2att(json_encode($children)),
|
||||
Convert::raw2xml(str_replace(["\n","\r"], "", $this->MenuTitle ?? ''))
|
||||
);
|
||||
foreach ($flags as $class => $data) {
|
||||
if (is_string($data)) {
|
||||
$data = ['text' => $data];
|
||||
}
|
||||
$treeTitle .= sprintf(
|
||||
"<span class=\"badge %s\"%s>%s</span>",
|
||||
'status-' . Convert::raw2xml($class),
|
||||
(isset($data['title'])) ? sprintf(' title="%s"', Convert::raw2xml($data['title'])) : '',
|
||||
Convert::raw2xml($data['text'])
|
||||
);
|
||||
}
|
||||
|
||||
return $treeTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page in the current page stack of the given level. Level(1) will return the main menu item that
|
||||
* we're currently inside, etc.
|
||||
@ -3214,36 +3049,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
return parent::plural_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate link to this page's icon
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPageIconURL()
|
||||
{
|
||||
$icon = $this->config()->get('icon');
|
||||
if (!$icon) {
|
||||
return null;
|
||||
}
|
||||
if (strpos($icon ?? '', 'data:image/') !== false) {
|
||||
return $icon;
|
||||
}
|
||||
|
||||
// Icon is relative resource
|
||||
$iconResource = ModuleResourceLoader::singleton()->resolveResource($icon);
|
||||
if ($iconResource instanceof ModuleResource) {
|
||||
return $iconResource->getURL();
|
||||
}
|
||||
|
||||
// Full path to file
|
||||
if (Director::fileExists($icon)) {
|
||||
return ModuleResourceLoader::resourceURL($icon);
|
||||
}
|
||||
|
||||
// Skip invalid files
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description for this page type
|
||||
*
|
||||
@ -3343,17 +3148,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache key for creatableChildPages() method
|
||||
*
|
||||
* @param int $memberID
|
||||
* @return string
|
||||
*/
|
||||
protected function generateChildrenCacheKey($memberID)
|
||||
{
|
||||
return md5($memberID . '_' . __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of excluded root URL segments
|
||||
*
|
||||
|
@ -30,7 +30,7 @@ class VirtualPage extends Page
|
||||
{
|
||||
private static $description = 'Displays the content of another page';
|
||||
|
||||
private static $icon_class = 'font-icon-p-virtual';
|
||||
private static $cms_icon_class = 'font-icon-p-virtual';
|
||||
|
||||
public static $virtualFields;
|
||||
|
||||
|
@ -140,7 +140,7 @@ class BrokenLinksReport extends Report
|
||||
'title' => _t(__CLASS__ . '.ColumnURL', 'URL'),
|
||||
'formatting' => function ($value, $item) {
|
||||
/** @var SiteTree $item */
|
||||
$liveLink = $item->AbsoluteLiveLink;
|
||||
$liveLink = $item->getAbsoluteLiveLink();
|
||||
$stageLink = $item->AbsoluteLink();
|
||||
return sprintf(
|
||||
'%s <a href="%s">%s</a>',
|
||||
|
@ -256,6 +256,8 @@ en:
|
||||
TABCONTENT: 'Main content'
|
||||
TABDEPENDENT: 'Dependent pages'
|
||||
TOPLEVEL: 'Site Content (Top Level)'
|
||||
TREETITLE: 'Page name'
|
||||
TREETYPE: 'Page type'
|
||||
UNTITLED: 'Untitled {pagetype}'
|
||||
URLSegment: 'URL segment'
|
||||
UntitledDependentObject: 'Untitled {instanceType}'
|
||||
|
@ -0,0 +1,47 @@
|
||||
<div class="flexbox-area-grow cms-content $Controller.BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||
<form $FormAttributes data-layout-type="border">
|
||||
<div class="toolbar toolbar--north">
|
||||
<div class="toolbar__navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb__item">
|
||||
<% if $Controller.SectionTitle %>
|
||||
$Controller.SectionTitle
|
||||
<% else %>
|
||||
<%t SilverStripe\CMS\Controllers\CMSMain.Title 'Data Models'%>
|
||||
<% end_if %>
|
||||
</li>
|
||||
<li class="breadcrumb__item breadcrumb__item--last breadcrumb__item--no-crumb">
|
||||
<h2 class="breadcrumb__item-title breadcrumb__item-title--last">
|
||||
<%t SilverStripe\Admin\LeftAndMain.NewRecord 'New {name}' name=$Controller.getRecord('singleton').i18n_singular_name() %>
|
||||
</h2>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel--padded panel--scrollable flexbox-area-grow">
|
||||
<% if $Message %>
|
||||
<p id="{$FormName}_error" class="alert $AlertType">$Message</p>
|
||||
<% else %>
|
||||
<p id="{$FormName}_error" class="alert $AlertType" style="display: none"></p>
|
||||
<% end_if %>
|
||||
|
||||
<fieldset>
|
||||
<% if $Legend %><legend>$Legend</legend><% end_if %>
|
||||
<% loop $Fields %>
|
||||
$FieldHolder
|
||||
<% end_loop %>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="toolbar--south">
|
||||
<% if $Actions %>
|
||||
<div class="btn-toolbar">
|
||||
<% loop $Actions %>
|
||||
$Field
|
||||
<% end_loop %>
|
||||
</div>
|
||||
<% end_if %>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,27 +1,28 @@
|
||||
<% if $CurrentRecord %>
|
||||
<%-- Left and right panel panel only --%>
|
||||
<div id="pages-controller-cms-content" class="has-panel cms-content flexbox-area-grow fill-width fill-height $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content" data-ignore-tab-state="true">
|
||||
$Tools
|
||||
|
||||
<div class="fill-height flexbox-area-grow">
|
||||
<div class="cms-content-header north">
|
||||
<div class="cms-content-header-info flexbox-area-grow vertical-align-items">
|
||||
<a href="$BreadcrumbsBackLink" class="btn btn-secondary btn--no-text font-icon-left-open-big hidden-lg-up toolbar__back-button"></a>
|
||||
<% include SilverStripe\\Admin\\BackLink_Button Backlink=$BreadcrumbsBacklink %>
|
||||
<% include SilverStripe\\Admin\\CMSBreadcrumbs %>
|
||||
</div>
|
||||
|
||||
<div class="cms-content-header-tabs cms-tabset">
|
||||
<ul class="cms-tabset-nav-primary nav nav-tabs">
|
||||
<li class="nav-item content-treeview<% if $TabIdentifier == 'edit' %> ui-tabs-active<% end_if %>">
|
||||
<a href="$LinkPageEdit" class="nav-link cms-panel-link" title="Form_EditForm" data-href="$LinkPageEdit">
|
||||
<a href="$LinkRecordEdit" class="nav-link cms-panel-link" title="Form_EditForm" data-href="$LinkRecordEdit">
|
||||
<%t SilverStripe\\CMS\\Controllers\\CMSMain.TabContent 'Content' %>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item content-listview<% if $TabIdentifier == 'settings' %> ui-tabs-active<% end_if %>">
|
||||
<a href="$LinkPageSettings" class="nav-link cms-panel-link" title="Form_EditForm" data-href="$LinkPageSettings">
|
||||
<a href="$LinkRecordSettings" class="nav-link cms-panel-link" title="Form_EditForm" data-href="$LinkRecordSettings">
|
||||
<%t SilverStripe\\CMS\\Controllers\\CMSMain.TabSettings 'Settings' %>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item content-listview<% if $TabIdentifier == 'history' %> ui-tabs-active<% end_if %>">
|
||||
<a href="$LinkPageHistory" class="nav-link cms-panel-link" title="Form_EditForm" data-href="$LinkPageHistory">
|
||||
<a href="$LinkRecordHistory" class="nav-link cms-panel-link" title="Form_EditForm" data-href="$LinkRecordHistory">
|
||||
<%t SilverStripe\\CMS\\Controllers\\CMSMain.TabHistory 'History' %>
|
||||
</a>
|
||||
</li>
|
||||
@ -34,3 +35,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<%-- Left panel only --%>
|
||||
<div id="pages-controller-cms-content" class="flexbox-area-grow fill-height cms-content $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_LeftPanel %>
|
||||
</div>
|
||||
<% end_if %>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<div class="toolbar toolbar--content cms-content-toolbar">
|
||||
<div class="btn-toolbar cms-actions-buttons-row">
|
||||
<% if not $TreeIsFiltered %>
|
||||
<a class="btn btn-primary cms-content-addpage-button tool-button font-icon-plus" href="$LinkPageAdd" data-url-addpage="{$LinkPageAdd('', 'ParentID=%s')}"><%t SilverStripe\CMS\Controllers\CMSMain.AddNewButton 'Add new' %></a>
|
||||
<a class="btn btn-primary cms-content-addpage-button tool-button font-icon-plus" href="$LinkRecordAdd" data-url-addpage="{$LinkRecordAdd('', 'ParentID=%s')}">
|
||||
<%t SilverStripe\Admin\\LeftAndMain.AddNew 'Add new {name}' name=$getRecord('singleton').i18n_singular_name().lowercase %>
|
||||
</a>
|
||||
|
||||
<% if $View == 'Tree' %>
|
||||
<button type="button" class="cms-content-batchactions-button btn btn-secondary tool-button font-icon-check-mark-2 btn--last" data-toolid="batch-actions">
|
||||
@ -10,7 +12,7 @@
|
||||
<% end_if %>
|
||||
<% end_if %>
|
||||
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_ViewControls PJAXTarget='Content-PageList' %>
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_ViewControls PJAXTarget='Content-RecordList' %>
|
||||
</div>
|
||||
|
||||
|
@ -22,11 +22,6 @@
|
||||
<% loop $Actions %>
|
||||
$FieldHolder
|
||||
<% end_loop %>
|
||||
<% if $Controller.LinkPreview %>
|
||||
<a href="$Controller.LinkPreview" target="_cmsPreview" class="cms-preview-toggle-link ss-ui-button" data-icon="preview">
|
||||
<%t SilverStripe\Admin\LeftAndMain.PreviewButton 'Preview' %> »
|
||||
</a>
|
||||
<% end_if %>
|
||||
|
||||
<% include SilverStripe\\Admin\\LeftAndMain_ViewModeSelector SelectID="preview-mode-dropdown-in-content", ExtraClass='' %>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="view-controls">
|
||||
<% if not $TreeIsFiltered %>
|
||||
<button id="filters-button" class="btn btn-secondary icon-button font-icon-search btn--icon-large no-text" title="<% _t('SilverStripe\CMS\Controllers\CMSPagesController.FILTER', 'Filter') %>"></button>
|
||||
<button id="filters-button" class="btn btn-secondary icon-button font-icon-search btn--icon-large no-text" title="<% _t('SilverStripe\CMS\Controllers\CMSMain.FILTER', 'Filter') %>"></button>
|
||||
<% end_if %>
|
||||
</div>
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
<div class="cms-content-header north vertical-align-items">
|
||||
<div class="cms-content-header-info fill-width vertical-align-items">
|
||||
<% if $TreeIsFiltered %>
|
||||
<% include SilverStripe\\Admin\\BackLink_Button Backlink=$BreadcrumbsBacklink %>
|
||||
<% end_if %>
|
||||
<% if $CurrentRecord %>
|
||||
<%-- Explicit breadcrumb item for this menu section --%>
|
||||
<div class="section-heading flexbox-area-grow">
|
||||
<span class="section-label">$MenuCurrentItem.Title</span>
|
||||
</div>
|
||||
<% else %>
|
||||
<%-- Full breadcrumbs (useful for tree view which isn't available when viewing an edit form) --%>
|
||||
<% include SilverStripe\\Admin\\CMSBreadcrumbs %>
|
||||
<% end_if %>
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_Filter %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="<% if $CurrentRecord %>panel panel--scrollable cms-panel-content<% else %>cms-content-fields ui-widget-content cms-panel-padded<% end_if %> flexbox-area-grow fill-height">
|
||||
<div class="cms-content-filters<% if not $TreeIsFiltered %> cms-content-filters--hidden<% end_if %>">
|
||||
<div class="search-holder search-holder--cms" data-schema="$SearchFieldSchema"></div>
|
||||
</div>
|
||||
$RecordList
|
||||
</div>
|
@ -1,6 +1,6 @@
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSPagesController_ContentToolActions %>
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_ContentToolActions %>
|
||||
|
||||
<div class="ss-dialog cms-page-add-form-dialog cms-dialog-content" id="cms-page-add-form" title="<%t SilverStripe\\CMS\\Controllers\\CMSMain.AddNew 'Add new page' %>">
|
||||
<div class="ss-dialog cms-page-add-form-dialog cms-dialog-content" id="cms-page-add-form" title="<%t SilverStripe\Admin\\LeftAndMain.AddNew 'Add new {name}' name=$getRecord('singleton').i18n_singular_name().lowercase %>">
|
||||
$AddForm
|
||||
</div>
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_PageList %>
|
@ -4,7 +4,7 @@
|
||||
<% if $limited %>
|
||||
<ul><li class="readonly">
|
||||
<span class="item">
|
||||
<%t SilverStripe\\CMS\\Controllers\\CMSMain.TOO_MANY_PAGES 'Too many pages' %>
|
||||
<%t SilverStripe\\CMS\\Controllers\\CMSMain.TOO_MANY_RECORDS 'Too many records' %>
|
||||
(<a href="{$listViewLink.ATT}" class="subtree-list-link" data-id="$node.ID" data-pjax-target="Content"><%t SilverStripe\\CMS\\Controllers\\CMSMain.SHOW_AS_LIST 'show as list' %></a>)
|
||||
</span>
|
||||
</li></ul>
|
||||
|
@ -1,26 +1,13 @@
|
||||
<%-- If we're editing a record, include the left panel and allow it to be collapsed --%>
|
||||
<% if $CurrentRecord %>
|
||||
<div class="cms-content-tools fill-height cms-panel cms-panel-layout" data-expandOnClick="true" data-layout-type="border" id="cms-content-tools-CMSMain">
|
||||
<div class="cms-content-header north vertical-align-items">
|
||||
<div class="cms-content-header-info vertical-align-items fill-width">
|
||||
<div class="section-heading flexbox-area-grow">
|
||||
<span class="section-label"><a href="$LinkPages">{$MenuCurrentItem.Title}</a></span>
|
||||
</div>
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_Filter %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel--scrollable flexbox-area-grow fill-height cms-panel-content">
|
||||
<div class="cms-content-filters cms-content-filters--hidden">
|
||||
<div
|
||||
class="search-holder search-holder--cms"
|
||||
data-schema="$SearchFieldSchema"
|
||||
></div>
|
||||
</div>
|
||||
$PageListSidebar
|
||||
</div>
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_LeftPanel %>
|
||||
<div class="cms-panel-content-collapsed">
|
||||
<h3 class="cms-panel-header">$SiteConfig.Title</h3>
|
||||
<h3 class="cms-panel-header">$CMSTreeTitle</h3>
|
||||
</div>
|
||||
<div class="toolbar toolbar--south cms-panel-toggle">
|
||||
<a class="toggle-expand" href="#"><span>»</span></a>
|
||||
<a class="toggle-collapse" href="#"><span>«</span></a>
|
||||
</div>
|
||||
</div>
|
||||
<% end_if %>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<li id="record-{$node.ID}" data-id="{$node.ID}" data-pagetype="{$node.ClassName}" class="$markingClasses $extraClass"><ins class="jstree-icon font-icon-right-dir"> </ins>
|
||||
<a href="{$node.CMSEditLink.ATT}" title="{$Title.ATT}"><ins class="jstree-icon font-icon-drag-handle"> </ins>
|
||||
<span class="text">{$node.TreeTitle}</span>
|
||||
<span class="text">{$RecordTreeMarkup($node)}</span>
|
||||
</a>
|
||||
$SubTree
|
||||
</li>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSPagesController_ContentToolActions View='Tree' %>
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_ContentToolActions View='Tree' %>
|
||||
|
||||
<div class="ss-dialog cms-page-add-form-dialog cms-dialog-content" id="cms-page-add-form" title="<%t SilverStripe\CMS\Controllers\CMSMain.AddNew 'Add new page' %>">
|
||||
<div class="ss-dialog cms-page-add-form-dialog cms-dialog-content" id="cms-page-add-form" title="<%t SilverStripe\Admin\LeftAndMain.AddNew 'Add new {name}' name=$getRecord('singleton').i18n_singular_name().lowercase %>">
|
||||
$AddForm
|
||||
</div>
|
||||
|
||||
@ -17,15 +17,15 @@ $ExtraTreeTools
|
||||
data-url-tree="$LinkWithSearch($Link('getsubtree')).ATT"
|
||||
data-url-savetreenode="$Link('savetreenode').ATT"
|
||||
data-url-updatetreenodes="$Link('updatetreenodes').ATT"
|
||||
data-url-addpage="{$LinkPageAdd('AddForm/?action_doAdd=1', 'ParentID=%s&PageType=%s').ATT}"
|
||||
data-url-editpage="$LinkPageEdit('%s').ATT"
|
||||
data-url-addpage="{$LinkRecordAdd('AddForm/?action_doAdd=1', 'ParentID=%s&RecordType=%s').ATT}"
|
||||
data-url-editpage="$LinkRecordEdit('%s').ATT"
|
||||
data-url-duplicate="{$Link('duplicate/%s').ATT}"
|
||||
data-url-duplicatewithchildren="{$Link('duplicatewithchildren/%s').ATT}"
|
||||
data-url-listview="{$Link('?view=list').ATT}"
|
||||
data-hints="$SiteTreeHints.ATT"
|
||||
data-hints="$TreeHints.ATT"
|
||||
data-childfilter="$Link('childfilter').ATT"
|
||||
data-extra-params="SecurityID=$SecurityID.ATT">
|
||||
$SiteTreeAsUL
|
||||
$TreeAsUL
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
@ -33,14 +33,14 @@ $ExtraTreeTools
|
||||
data-url-tree="$LinkWithSearch($Link('getsubtree')).ATT"
|
||||
data-url-savetreenode="$Link('savetreenode').ATT"
|
||||
data-url-updatetreenodes="$Link('updatetreenodes').ATT"
|
||||
data-url-addpage="{$LinkPageAdd('AddForm/?action_doAdd=1', 'ParentID=%s&PageType=%s').ATT}"
|
||||
data-url-editpage="$LinkPageEdit('%s').ATT"
|
||||
data-url-addpage="{$LinkRecordAdd('AddForm/?action_doAdd=1', 'ParentID=%s&RecordType=%s').ATT}"
|
||||
data-url-editpage="$LinkRecordEdit('%s').ATT"
|
||||
data-url-duplicate="{$Link('duplicate/%s').ATT}"
|
||||
data-url-duplicatewithchildren="{$Link('duplicatewithchildren/%s').ATT}"
|
||||
data-url-listview="{$Link('?view=list').ATT}"
|
||||
data-hints="$SiteTreeHints.ATT"
|
||||
data-hints="$TreeHints.ATT"
|
||||
data-childfilter="$Link('childfilter').ATT"
|
||||
data-extra-params="SecurityID=$SecurityID.ATT">
|
||||
$SiteTreeAsUL
|
||||
$TreeAsUL
|
||||
</div>
|
||||
<% end_if %>
|
||||
|
@ -1,18 +1,18 @@
|
||||
<div class="view-controls view-controls--{$ViewState}">
|
||||
<% if not $TreeIsFiltered %>
|
||||
<%-- Change to data-pjax-target="Content-PageList" to enable in-edit listview --%>
|
||||
<%-- Change to data-pjax-target="Content-RecordList" to enable in-edit listview --%>
|
||||
<a class="page-view-link btn btn-secondary btn--icon-sm btn--no-text font-icon-tree"
|
||||
href="$LinkTreeView.ATT"
|
||||
data-view="treeview"
|
||||
data-pjax-target="$PJAXTarget.ATT"
|
||||
title="<%t SilverStripe\CMS\Controllers\CMSPagesController.TreeView 'Tree View' %>"
|
||||
title="<%t SilverStripe\CMS\Controllers\CMSMain.TreeView 'Tree View' %>"
|
||||
></a>
|
||||
|
||||
<a class="page-view-link btn btn-secondary btn--icon-sm btn--no-text font-icon-list"
|
||||
href="$LinkListView.ATT"
|
||||
data-view="listview"
|
||||
data-pjax-target="$PJAXTarget.ATT"
|
||||
title="<%t SilverStripe\CMS\Controllers\CMSPagesController.ListView 'List View' %>"
|
||||
title="<%t SilverStripe\CMS\Controllers\CMSMain.ListView 'List View' %>"
|
||||
></a>
|
||||
<% end_if %>
|
||||
</div>
|
||||
|
@ -1,45 +0,0 @@
|
||||
<div class="flexbox-area-grow cms-content $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||
<% with $AddForm %>
|
||||
<form $FormAttributes data-layout-type="border">
|
||||
<div class="toolbar toolbar--north">
|
||||
<div class="toolbar__navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb__item">
|
||||
<%t SilverStripe\CMS\Controllers\CMSPagesController.MENUTITLE 'Pages'%>
|
||||
</li>
|
||||
<li class="breadcrumb__item breadcrumb__item--last breadcrumb__item--no-crumb">
|
||||
<h2 class="breadcrumb__item-title breadcrumb__item-title--last">
|
||||
<%t SilverStripe\CMS\Controllers\CMSPageAddController.Title 'Add page' %>
|
||||
</h2>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel--padded panel--scrollable flexbox-area-grow">
|
||||
<% if $Message %>
|
||||
<p id="{$FormName}_error" class="alert $AlertType">$Message</p>
|
||||
<% else %>
|
||||
<p id="{$FormName}_error" class="alert $AlertType" style="display: none"></p>
|
||||
<% end_if %>
|
||||
|
||||
<fieldset>
|
||||
<% if $Legend %><legend>$Legend</legend><% end_if %>
|
||||
<% loop $Fields %>
|
||||
$FieldHolder
|
||||
<% end_loop %>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="toolbar--south">
|
||||
<% if $Actions %>
|
||||
<div class="btn-toolbar">
|
||||
<% loop $Actions %>
|
||||
$Field
|
||||
<% end_loop %>
|
||||
</div>
|
||||
<% end_if %>
|
||||
</div>
|
||||
</form>
|
||||
<% end_with %>
|
||||
</div>
|
@ -1,18 +0,0 @@
|
||||
<div id="pages-controller-cms-content" class="flexbox-area-grow fill-height cms-content $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||
<div class="cms-content-header north vertical-align-items">
|
||||
<div class="cms-content-header-info fill-width vertical-align-items">
|
||||
<% if $TreeIsFiltered %>
|
||||
<a class="btn btn-secondary font-icon-left-open-big toolbar__back-button btn--no-text" href="$BreadcrumbsBacklink">
|
||||
<span class="sr-only"><%t SilverStripe\Admin\LeftAndMain.NavigateUp "Return to Pages" %></span>
|
||||
</a>
|
||||
<% end_if %>
|
||||
<% include SilverStripe\\Admin\\CMSBreadcrumbs %>
|
||||
<% include SilverStripe\\CMS\\Controllers\\CMSMain_Filter %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flexbox-area-grow fill-height cms-content-fields ui-widget-content cms-panel-padded">
|
||||
$Tools
|
||||
$PageList
|
||||
</div>
|
||||
</div>
|
@ -1,3 +0,0 @@
|
||||
<div id="cms-content-filters-CMSPagesController" class="cms-content-filters<% if not $TreeIsFiltered %> cms-content-filters--hidden<% end_if %>">
|
||||
<div class="search-holder search-holder--cms" data-schema="$SearchFieldSchema"></div>
|
||||
</div>
|
@ -44,15 +44,15 @@ class CMSMainTest extends FunctionalTest
|
||||
}
|
||||
}
|
||||
|
||||
public function testSiteTreeHints()
|
||||
public function testTreeHints()
|
||||
{
|
||||
$cache = Injector::inst()->get(CacheInterface::class . '.CMSMain_SiteTreeHints');
|
||||
$cache = Injector::inst()->get(CacheInterface::class . '.CMSMain_TreeHints');
|
||||
// Login as user with root creation privileges
|
||||
$user = $this->objFromFixture(Member::class, 'rootedituser');
|
||||
Security::setCurrentUser($user);
|
||||
$cache->clear();
|
||||
|
||||
$rawHints = singleton(CMSMain::class)->SiteTreeHints();
|
||||
$rawHints = singleton(CMSMain::class)->TreeHints();
|
||||
$this->assertNotNull($rawHints);
|
||||
|
||||
$rawHints = preg_replace('/^"(.*)"$/', '$1', Convert::xml2raw($rawHints) ?? '');
|
||||
@ -611,7 +611,7 @@ class CMSMainTest extends FunctionalTest
|
||||
$this->assertEquals('Class A', $newPage->Title);
|
||||
}
|
||||
|
||||
public function testSiteTreeHintsCache()
|
||||
public function testTreeHintsCache()
|
||||
{
|
||||
$cms = CMSMain::create();
|
||||
/** @var Member $user */
|
||||
@ -635,31 +635,31 @@ class CMSMainTest extends FunctionalTest
|
||||
|
||||
// Initially, cache misses (1)
|
||||
Injector::inst()->registerService($mockPageMissesCache, $pageClass);
|
||||
$hints = $cms->SiteTreeHints();
|
||||
$hints = $cms->TreeHints();
|
||||
$this->assertNotNull($hints);
|
||||
|
||||
// Now it hits
|
||||
Injector::inst()->registerService($mockPageHitsCache, $pageClass);
|
||||
$hints = $cms->SiteTreeHints();
|
||||
$hints = $cms->TreeHints();
|
||||
$this->assertNotNull($hints);
|
||||
|
||||
// Mutating member record invalidates cache. Misses (2)
|
||||
$user->FirstName = 'changed';
|
||||
$user->write();
|
||||
Injector::inst()->registerService($mockPageMissesCache, $pageClass);
|
||||
$hints = $cms->SiteTreeHints();
|
||||
$hints = $cms->TreeHints();
|
||||
$this->assertNotNull($hints);
|
||||
|
||||
// Now it hits again
|
||||
Injector::inst()->registerService($mockPageHitsCache, $pageClass);
|
||||
$hints = $cms->SiteTreeHints();
|
||||
$hints = $cms->TreeHints();
|
||||
$this->assertNotNull($hints);
|
||||
|
||||
// Different user. Misses. (3)
|
||||
$user = $this->objFromFixture(Member::class, 'allcmssectionsuser');
|
||||
Security::setCurrentUser($user);
|
||||
Injector::inst()->registerService($mockPageMissesCache, $pageClass);
|
||||
$hints = $cms->SiteTreeHints();
|
||||
$hints = $cms->TreeHints();
|
||||
$this->assertNotNull($hints);
|
||||
}
|
||||
|
||||
@ -703,21 +703,21 @@ class CMSMainTest extends FunctionalTest
|
||||
);
|
||||
}
|
||||
|
||||
public function testCanOrganiseSitetree()
|
||||
public function testCanOrganiseTree()
|
||||
{
|
||||
$cms = CMSMain::create();
|
||||
|
||||
$this->assertFalse($cms->CanOrganiseSitetree());
|
||||
$this->assertFalse($cms->CanOrganiseTree());
|
||||
|
||||
$this->logInWithPermission('CMS_ACCESS_CMSMain');
|
||||
$this->assertFalse($cms->CanOrganiseSitetree());
|
||||
$this->assertFalse($cms->CanOrganiseTree());
|
||||
|
||||
$this->logOut();
|
||||
$this->logInWithPermission('SITETREE_REORGANISE');
|
||||
$this->assertTrue($cms->CanOrganiseSitetree());
|
||||
$this->assertTrue($cms->CanOrganiseTree());
|
||||
|
||||
$this->logOut();
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$this->assertTrue($cms->CanOrganiseSitetree());
|
||||
$this->assertTrue($cms->CanOrganiseTree());
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
$f = new CMSSiteTreeFilter_Search();
|
||||
$results = $f->pagesIncluded();
|
||||
|
||||
$this->assertTrue($f->isPageIncluded($page1));
|
||||
$this->assertTrue($f->isPageIncluded($page2));
|
||||
$this->assertTrue($f->isRecordIncluded($page1));
|
||||
$this->assertTrue($f->isRecordIncluded($page2));
|
||||
}
|
||||
|
||||
public function testSearchFilterByTitle()
|
||||
@ -36,8 +36,8 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
$f = new CMSSiteTreeFilter_Search(['Title' => 'Page 1']);
|
||||
$results = $f->pagesIncluded();
|
||||
|
||||
$this->assertTrue($f->isPageIncluded($page1));
|
||||
$this->assertFalse($f->isPageIncluded($page2));
|
||||
$this->assertTrue($f->isRecordIncluded($page1));
|
||||
$this->assertFalse($f->isRecordIncluded($page2));
|
||||
$this->assertEquals(1, count($results ?? []));
|
||||
$this->assertEquals(
|
||||
['ID' => $page1->ID, 'ParentID' => 0],
|
||||
@ -50,10 +50,10 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
$page = $this->objFromFixture(SiteTree::class, 'page8');
|
||||
|
||||
$filter = CMSSiteTreeFilter_Search::create(['Term' => 'lake-wanaka+adventure']);
|
||||
$this->assertTrue($filter->isPageIncluded($page));
|
||||
$this->assertTrue($filter->isRecordIncluded($page));
|
||||
|
||||
$filter = CMSSiteTreeFilter_Search::create(['URLSegment' => 'lake-wanaka+adventure']);
|
||||
$this->assertTrue($filter->isPageIncluded($page));
|
||||
$this->assertTrue($filter->isRecordIncluded($page));
|
||||
}
|
||||
|
||||
public function testIncludesParentsForNestedMatches()
|
||||
@ -64,8 +64,8 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
$f = new CMSSiteTreeFilter_Search(['Title' => 'Page 3b']);
|
||||
$results = $f->pagesIncluded();
|
||||
|
||||
$this->assertTrue($f->isPageIncluded($parent));
|
||||
$this->assertTrue($f->isPageIncluded($child));
|
||||
$this->assertTrue($f->isRecordIncluded($parent));
|
||||
$this->assertTrue($f->isRecordIncluded($child));
|
||||
$this->assertEquals(1, count($results ?? []));
|
||||
$this->assertEquals(
|
||||
['ID' => $child->ID, 'ParentID' => $parent->ID],
|
||||
@ -91,8 +91,8 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
$f = new CMSSiteTreeFilter_ChangedPages(['Term' => 'Changed']);
|
||||
$results = $f->pagesIncluded();
|
||||
|
||||
$this->assertTrue($f->isPageIncluded($changedPage));
|
||||
$this->assertFalse($f->isPageIncluded($unchangedPage));
|
||||
$this->assertTrue($f->isRecordIncluded($changedPage));
|
||||
$this->assertFalse($f->isRecordIncluded($unchangedPage));
|
||||
$this->assertEquals(1, count($results ?? []));
|
||||
$this->assertEquals(
|
||||
['ID' => $changedPage->ID, 'ParentID' => 0],
|
||||
@ -130,11 +130,11 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
);
|
||||
|
||||
$f = new CMSSiteTreeFilter_DeletedPages(['Term' => 'Page']);
|
||||
$this->assertTrue($f->isPageIncluded($deletedPage));
|
||||
$this->assertTrue($f->isRecordIncluded($deletedPage));
|
||||
|
||||
// Check that only changed pages are returned
|
||||
$f = new CMSSiteTreeFilter_DeletedPages(['Term' => 'No Matches']);
|
||||
$this->assertFalse($f->isPageIncluded($deletedPage));
|
||||
$this->assertFalse($f->isRecordIncluded($deletedPage));
|
||||
}
|
||||
|
||||
public function testStatusDraftPagesFilter()
|
||||
@ -148,16 +148,16 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
|
||||
// Check draft page is shown
|
||||
$f = new CMSSiteTreeFilter_StatusDraftPages(['Term' => 'Page']);
|
||||
$this->assertTrue($f->isPageIncluded($draftPage));
|
||||
$this->assertTrue($f->isRecordIncluded($draftPage));
|
||||
|
||||
// Check filter respects parameters
|
||||
$f = new CMSSiteTreeFilter_StatusDraftPages(['Term' => 'No Match']);
|
||||
$this->assertEmpty($f->isPageIncluded($draftPage));
|
||||
$this->assertEmpty($f->isRecordIncluded($draftPage));
|
||||
|
||||
// Ensures empty array returned if no data to show
|
||||
$f = new CMSSiteTreeFilter_StatusDraftPages();
|
||||
$draftPage->delete();
|
||||
$this->assertEmpty($f->isPageIncluded($draftPage));
|
||||
$this->assertEmpty($f->isRecordIncluded($draftPage));
|
||||
}
|
||||
|
||||
public function testDateFromToLastSameDate()
|
||||
@ -171,7 +171,7 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
'LastEditedTo' => $date,
|
||||
]);
|
||||
$this->assertTrue(
|
||||
$filter->isPageIncluded($draftPage),
|
||||
$filter->isRecordIncluded($draftPage),
|
||||
'Using the same date for from and to should show find that page'
|
||||
);
|
||||
}
|
||||
@ -189,16 +189,16 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
|
||||
// Check live-only page is included
|
||||
$f = new CMSSiteTreeFilter_StatusRemovedFromDraftPages(['LastEditedFrom' => '2000-01-01 00:00']);
|
||||
$this->assertTrue($f->isPageIncluded($removedDraftPage));
|
||||
$this->assertTrue($f->isRecordIncluded($removedDraftPage));
|
||||
|
||||
// Check filter is respected
|
||||
$f = new CMSSiteTreeFilter_StatusRemovedFromDraftPages(['LastEditedTo' => '1999-01-01 00:00']);
|
||||
$this->assertEmpty($f->isPageIncluded($removedDraftPage));
|
||||
$this->assertEmpty($f->isRecordIncluded($removedDraftPage));
|
||||
|
||||
// Ensures empty array returned if no data to show
|
||||
$f = new CMSSiteTreeFilter_StatusRemovedFromDraftPages();
|
||||
$removedDraftPage->delete();
|
||||
$this->assertEmpty($f->isPageIncluded($removedDraftPage));
|
||||
$this->assertEmpty($f->isRecordIncluded($removedDraftPage));
|
||||
}
|
||||
|
||||
public function testStatusDeletedFilter()
|
||||
@ -214,10 +214,10 @@ class CMSSiteTreeFilterTest extends SapphireTest
|
||||
|
||||
// Check deleted page is included
|
||||
$f = new CMSSiteTreeFilter_StatusDeletedPages(['Title' => 'Page']);
|
||||
$this->assertTrue($f->isPageIncluded($checkParentExists));
|
||||
$this->assertTrue($f->isRecordIncluded($checkParentExists));
|
||||
|
||||
// Check filter is respected
|
||||
$f = new CMSSiteTreeFilter_StatusDeletedPages(['Title' => 'Bobby']);
|
||||
$this->assertFalse($f->isPageIncluded($checkParentExists));
|
||||
$this->assertFalse($f->isRecordIncluded($checkParentExists));
|
||||
}
|
||||
}
|
||||
|
@ -1413,6 +1413,7 @@ class SiteTreeTest extends SapphireTest
|
||||
|
||||
public function testModifyStatusFlagByInheritance()
|
||||
{
|
||||
//@todo move this to CMSMain test
|
||||
$node = new SiteTreeTest_StageStatusInherit();
|
||||
$treeTitle = $node->getTreeTitle();
|
||||
$this->assertStringContainsString('InheritedTitle', $treeTitle);
|
||||
|
Loading…
Reference in New Issue
Block a user