diff --git a/README.md b/README.md index ed8acdd2..bc5e7ef2 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ class Page extends SiteTree 'HasOneLink' => Link::class, ]; - private static $has_many = [ + private static $many_many = [ 'HasManyLinks' => Link::class ]; @@ -88,6 +88,78 @@ class ExternalLinkExtension extends Extension ``` +The user can control and specify the links allowed for creation in each link field. The `setAllowedTypes` method only includes link types that have been provided as parameters. + +```php +addFieldsToTab( + 'Root.Main', + [ + MultiLinkField::create('HasManyLinks') + ->setAllowedTypes([ SiteTreeLink::class ]), + ], + ); + + return $fields; + } +} +``` + + +Additionally, users have the option to designate links for exclusion from the available choices in the specific field. The setDisabledTypes method omits link types provided as parameters from the complete list of link types. +By default, all types of links are available. + +```php +addFieldsToTab( + 'Root.Main', + [ + LinkField::create('HasOneLink') + ->setDisabledTypes([ FileLink::class ]), + ], + ); + + return $fields; + } +} +``` + ## Migrating from Shae Dawson's Linkable module https://github.com/sheadawson/silverstripe-linkable diff --git a/_config/graphql.yml b/_config/graphql.yml deleted file mode 100644 index a353b4d1..00000000 --- a/_config/graphql.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -Name: linkgraphql ---- -SilverStripe\GraphQL\Schema\Schema: - schemas: - admin: - src: - link: silverstripe/linkfield:_graphql diff --git a/_config/types.yml b/_config/types.yml deleted file mode 100644 index 84960245..00000000 --- a/_config/types.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -Name: linkfield-types ---- -SilverStripe\LinkField\Type\Registry: - types: - cms: - classname: SilverStripe\LinkField\Models\SiteTreeLink - enabled: true - external: - classname: SilverStripe\LinkField\Models\ExternalLink - enabled: true - file: - classname: SilverStripe\LinkField\Models\FileLink - enabled: true - email: - classname: SilverStripe\LinkField\Models\EmailLink - enabled: true - phone: - classname: SilverStripe\LinkField\Models\PhoneLink - enabled: true diff --git a/_graphql/queries.yml b/_graphql/queries.yml deleted file mode 100644 index 8010e060..00000000 --- a/_graphql/queries.yml +++ /dev/null @@ -1,3 +0,0 @@ -'readLinkTypes(keys: [ID])': - type: '[LinkType]' - resolver: ['SilverStripe\LinkField\GraphQL\LinkTypeResolver', 'resolve'] diff --git a/_graphql/types.yml b/_graphql/types.yml deleted file mode 100644 index 8af9de12..00000000 --- a/_graphql/types.yml +++ /dev/null @@ -1,5 +0,0 @@ -LinkType: - description: Describe a Type of Link that can be managed by a LinkField - fields: - key: ID - title: String! diff --git a/client/dist/js/bundle.js b/client/dist/js/bundle.js index d63e64dd..ce70ab44 100644 --- a/client/dist/js/bundle.js +++ b/client/dist/js/bundle.js @@ -1 +1 @@ -!function(){"use strict";var e={274:function(e,t,n){var r=l(n(521)),o=l(n(154));function l(e){return e&&e.__esModule?e:{default:e}}document.addEventListener("DOMContentLoaded",(()=>{(0,r.default)(),(0,o.default)()}))},521:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=u(n(648)),o=u(n(809)),l=u(n(852)),a=u(n(117)),i=u(n(606));function u(e){return e&&e.__esModule?e:{default:e}}var s=()=>{r.default.component.registerMany({LinkPicker:o.default,LinkField:l.default,"LinkModal.FormBuilderModal":a.default,"LinkModal.InsertMediaModal":i.default})};t.default=s},154:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=l(n(648)),o=l(n(689));function l(e){return e&&e.__esModule?e:{default:e}}var a=()=>{r.default.query.register("readLinkTypes",o.default)};t.default=a},852:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=g(n(363)),o=n(827),l=n(624),a=n(648),i=k(n(42)),u=k(n(809)),s=k(n(734)),d=k(n(686)),f=k(n(697)),c=g(n(123)),p=k(n(159)),y=k(n(510)),v=k(n(86)),m=k(n(754));function k(e){return e&&e.__esModule?e:{default:e}}function _(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(_=function(e){return e?n:t})(e)}function g(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 r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var a=o?Object.getOwnPropertyDescriptor(e,l):null;a&&(a.get||a.set)?Object.defineProperty(r,l,a):r[l]=e[l]}return r.default=e,n&&n.set(e,r),r}const O="SilverStripe\\LinkField\\Controllers\\LinkFieldController",h=e=>{var t;let{value:n=null,onChange:o,types:l,actions:a,isMulti:i=!1}=e;const[d,c]=(0,r.useState)({}),[v,k]=(0,r.useState)(0);let _=n;Array.isArray(_)||("number"==typeof _&&0!=_&&(_=[_]),_||(_=[])),(0,r.useEffect)((()=>{if(!v&&_.length>0){const e=[];for(const t of _)e.push(`itemIDs[]=${t}`);const t=`${y.default.getSection(O).form.linkForm.dataUrl}?${e.join("&")}`;p.default.get(t).then((e=>e.json())).then((e=>{c(e)}))}}),[v,n&&n.length]);const g=()=>{k(0)},h=e=>{k(0);const t=[..._];t.includes(e)||t.push(e),o(i?t:t[0]),a.toasts.success(m.default._t("LinkField.SAVE_SUCCESS","Saved link"))},b=e=>{const t=`${y.default.getSection(O).form.linkForm.deleteUrl}/${e}`;p.default.delete(t,{},{"X-SecurityID":y.default.get("SecurityID")}).then((()=>{a.toasts.success(m.default._t("LinkField.DELETE_SUCCESS","Deleted link"))})).catch((()=>{a.toasts.error(m.default._t("LinkField.DELETE_ERROR","Failed to delete link"))}));const n={...d};delete n[e],c(n),o(i?Object.keys(n):0)},M=i||0===Object.keys(d).length,j=Boolean(v);return r.default.createElement(r.default.Fragment,null,M&&r.default.createElement(u.default,{onModalSuccess:h,onModalClosed:g,types:l}),r.default.createElement("div",null," ",(()=>{const e=[];for(const i of _){var t,n,o,a;if(!d[i])continue;const u=l.hasOwnProperty(null===(t=d[i])||void 0===t?void 0:t.typeKey)?l[null===(n=d[i])||void 0===n?void 0:n.typeKey]:{};e.push(r.default.createElement(s.default,{key:i,id:i,title:null===(o=d[i])||void 0===o?void 0:o.Title,description:null===(a=d[i])||void 0===a?void 0:a.description,typeTitle:u.title||"",onClear:b,onClick:()=>{k(i)}}))}return e})()," "),j&&r.default.createElement(f.default,{types:l,typeKey:null===(t=d[v])||void 0===t?void 0:t.typeKey,isOpen:Boolean(v),onSuccess:h,onClosed:g,linkID:v}))};h.propTypes={value:v.default.oneOfType([v.default.arrayOf(v.default.number),v.default.number]),onChange:v.default.func.isRequired,types:v.default.objectOf(d.default).isRequired,actions:v.default.object.isRequired,isMulti:v.default.bool};var b=(0,o.compose)((0,a.injectGraphql)("readLinkTypes"),i.default,(0,l.connect)(null,(e=>({actions:{toasts:(0,o.bindActionCreators)(c,e)}}))))(h);t.default=b},606:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;s(n(754));var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=u(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var a=o?Object.getOwnPropertyDescriptor(e,l):null;a&&(a.get||a.set)?Object.defineProperty(r,l,a):r[l]=e[l]}r.default=e,n&&n.set(e,r);return r}(n(363)),o=s(n(475)),l=n(624),a=s(n(686)),i=s(n(86));function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(u=function(e){return e?n:t})(e)}function s(e){return e&&e.__esModule?e:{default:e}}function d(){return d=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let{type:t,editing:n,data:l,actions:a,onSubmit:i,...u}=e;if(!t)return!1;(0,r.useEffect)((()=>{n?a.initModal():a.reset()}),[n]);const s=l?{ID:l.FileID,Description:l.Title,TargetBlank:!!l.OpenInNew}:{};return r.default.createElement(o.default,d({isOpen:n,type:"insert-link",title:!1,bodyClassName:"modal__dialog",className:"insert-link__dialog-wrapper--internal",fileAttributes:s,onInsert:e=>{let{ID:n,Description:r,TargetBlank:o}=e;return i({FileID:n,Title:r,OpenInNew:o,typeKey:t.key},"",(()=>{}))}},u))};f.propTypes={type:a.default.isRequired,editing:i.default.bool.isRequired,data:i.default.object.isRequired,actions:i.default.object.isRequired,onClick:i.default.func.isRequired};var c=(0,l.connect)((function(){return{}}),(function(e){return{actions:{initModal:()=>e({type:"INIT_FORM_SCHEMA_STACK",payload:{formSchema:{type:"insert-link",nextType:"admin"}}}),reset:()=>e({type:"RESET"})}}}))(f);t.default=c},117:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=s(n(363)),o=s(n(912)),l=s(n(872)),a=s(n(902)),i=s(n(510)),u=s(n(86));function s(e){return e&&e.__esModule?e:{default:e}}const d=(e,t)=>{const{schemaUrl:n}=i.default.getSection("SilverStripe\\LinkField\\Controllers\\LinkFieldController").form.linkForm,r=l.default.parse(n),o=a.default.parse(r.query);o.typeKey=e;for(const e of["href","path","pathname"])r[e]=`${r[e]}/${t}`;return l.default.format({...r,search:a.default.stringify(o)})},f=e=>{let{typeTitle:t,typeKey:n,linkID:l=0,isOpen:a,onSuccess:i,onClosed:u}=e;if(!n)return!1;return r.default.createElement(o.default,{title:t,isOpen:a,schemaUrl:d(n,l),identifier:"Link.EditingLinkInfo",onSubmit:async(e,t,n)=>{const r=await n();if(!r.id.match(/\/schema\/linkfield\/([0-9]+)/)){const e=r.id.match(/\/linkForm\/([0-9]+)/),t=parseInt(e[1],10);i(t)}return Promise.resolve()},onClosed:u})};f.propTypes={typeTitle:u.default.string.isRequired,typeKey:u.default.string.isRequired,linkID:u.default.number,isOpen:u.default.bool.isRequired,onSuccess:u.default.func.isRequired,onClosed:u.default.func.isRequired};var c=f;t.default=c},809:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.Component=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=d(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var a=o?Object.getOwnPropertyDescriptor(e,l):null;a&&(a.get||a.set)?Object.defineProperty(r,l,a):r[l]=e[l]}r.default=e,n&&n.set(e,r);return r}(n(363)),o=s(n(86)),l=s(n(820)),a=s(n(97)),i=s(n(686)),u=s(n(697));function s(e){return e&&e.__esModule?e:{default:e}}function d(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(d=function(e){return e?n:t})(e)}const f=e=>{let{types:t,onModalSuccess:n,onModalClosed:o}=e;const[i,s]=(0,r.useState)(""),d=""!==i,f=(0,l.default)("link-picker","form-control"),c=Object.values(t);return r.default.createElement("div",{className:f},r.default.createElement(a.default,{types:c,onSelect:e=>{s(e)}}),d&&r.default.createElement(u.default,{types:t,typeKey:i,isOpen:d,onSuccess:e=>{s(""),n(e)},onClosed:()=>{"function"==typeof o&&o(),s("")}}))};t.Component=f,f.propTypes={types:o.default.objectOf(i.default).isRequired,onModalSuccess:o.default.func.isRequired,onModalClosed:o.default.func};var c=f;t.default=c},97:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=s(n(754)),o=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=u(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var a=o?Object.getOwnPropertyDescriptor(e,l):null;a&&(a.get||a.set)?Object.defineProperty(r,l,a):r[l]=e[l]}r.default=e,n&&n.set(e,r);return r}(n(363)),l=s(n(86)),a=n(127),i=s(n(686));function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(u=function(e){return e?n:t})(e)}function s(e){return e&&e.__esModule?e:{default:e}}const d=e=>{let{types:t,onSelect:n}=e;const[l,i]=(0,o.useState)(!1);return o.default.createElement(a.Dropdown,{isOpen:l,toggle:()=>i((e=>!e)),className:"link-picker__menu"},o.default.createElement(a.DropdownToggle,{className:"link-picker__menu-toggle font-icon-plus-1",caret:!0},r.default._t("LinkField.ADD_LINK","Add Link")),o.default.createElement(a.DropdownMenu,null,t.map((e=>{let{key:t,title:r}=e;return o.default.createElement(a.DropdownItem,{key:t,onClick:()=>n(t)},r)}))))};d.propTypes={types:l.default.arrayOf(i.default).isRequired,onSelect:l.default.func.isRequired};var f=d;t.default=f},734:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=i(n(754)),o=i(n(363)),l=i(n(86)),a=n(127);function i(e){return e&&e.__esModule?e:{default:e}}const u=e=>t=>{t.nativeEvent.stopImmediatePropagation(),t.preventDefault(),t.nativeEvent.preventDefault(),t.stopPropagation(),e&&e()},s=e=>{let{id:t,title:n,description:l,typeTitle:i,onClear:s,onClick:d}=e;return o.default.createElement("div",{className:classnames("link-picker__link","form-control")},o.default.createElement(a.Button,{className:"link-picker__button font-icon-link",color:"secondary",onClick:u(d)},o.default.createElement("div",{className:"link-picker__link-detail"},o.default.createElement("div",{className:"link-picker__title"},n),o.default.createElement("small",{className:"link-picker__type"},i,": ",o.default.createElement("span",{className:"link-picker__url"},l)))),o.default.createElement(a.Button,{className:"link-picker__clear",color:"link",onClick:u((()=>s(t)))},r.default._t("LinkField.CLEAR","Clear")))};s.propTypes={id:l.default.number.isRequired,title:l.default.string,description:l.default.string,typeTitle:l.default.string.isRequired,onClear:l.default.func.isRequired,onClick:l.default.func.isRequired};var d=s;t.default=d},697:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=i(n(363)),o=n(648),l=i(n(86)),a=i(n(686));function i(e){return e&&e.__esModule?e:{default:e}}const u=e=>{let{types:t,typeKey:n,linkID:l=0,isOpen:a,onSuccess:i,onClosed:u}=e;if(!n)return!1;const s=t.hasOwnProperty(n)?t[n]:{},d=s&&s.hasOwnProperty("handlerName")?s.handlerName:"FormBuilderModal",f=(0,o.loadComponent)(`LinkModal.${d}`);return r.default.createElement(f,{typeTitle:s.title||"",typeKey:n,linkID:l,isOpen:a,onSuccess:i,onClosed:u})};u.propTypes={types:l.default.objectOf(a.default).isRequired,typeKey:l.default.string.isRequired,linkID:l.default.number,isOpen:l.default.bool.isRequired,onSuccess:l.default.func.isRequired,onClosed:l.default.func.isRequired};var s=u;t.default=s},41:function(e,t,n){var r=i(n(311)),o=i(n(363)),l=i(n(691)),a=n(648);function i(e){return e&&e.__esModule?e:{default:e}}function u(){return u=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e(".js-injector-boot .entwine-linkfield").entwine({Component:null,Root:null,onmatch(){const e=this.closest(".cms-content").attr("id"),t=e?{context:e}:{},n=this.data("schema-component"),r=(0,a.loadComponent)(n,t);this.setComponent(r),this.setRoot(l.default.createRoot(this[0])),this._super(),this.refresh()},refresh(){const e=this.getProps();this.getInputField().val(e.value);const t=this.getComponent();this.getRoot().render(o.default.createElement(t,u({},e,{noHolder:!0})))},handleChange(e){this.getInputField().data("value",e),this.refresh()},getProps(){return{value:this.getInputField().data("value"),onChange:this.handleChange.bind(this),isMulti:this.data("is-multi")??!1}},getInputField(){const t=this.data("field-id");return e(`#${t}`)},onunmatch(){const e=this.getRoot();e&&e.unmount()}})}))},689:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n(648);const o={props(e){const{data:{error:t,readLinkTypes:n,loading:r}}=e,o=t&&t.graphQLErrors&&t.graphQLErrors.map((e=>e.message));return{loading:r,types:n?n.reduce(((e,t)=>({...e,[t.key]:t})),{}):{},graphQLErrors:o}}},{READ:l}=r.graphqlTemplates;var a={apolloConfig:o,templateName:l,pluralName:"LinkTypes",pagination:!1,params:{keys:"[ID]"},args:{root:{keys:"keys"}},fields:["key","title"]};t.default=a},686:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,o=(r=n(86))&&r.__esModule?r:{default:r};var l=o.default.shape({key:o.default.string.isRequired,title:o.default.string.isRequired});t.default=l},159:function(e){e.exports=Backend},510:function(e){e.exports=Config},42:function(e){e.exports=FieldHolder},912:function(e){e.exports=FormBuilderModal},648:function(e){e.exports=Injector},475:function(e){e.exports=InsertMediaModal},872:function(e){e.exports=NodeUrl},86:function(e){e.exports=PropTypes},363:function(e){e.exports=React},691:function(e){e.exports=ReactDomClient},624:function(e){e.exports=ReactRedux},127:function(e){e.exports=Reactstrap},827:function(e){e.exports=Redux},123:function(e){e.exports=ToastsActions},820:function(e){e.exports=classnames},754:function(e){e.exports=i18n},311:function(e){e.exports=jQuery},902:function(e){e.exports=qs}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var l=t[r]={exports:{}};return e[r](l,l.exports,n),l.exports}n(274),n(41)}(); \ No newline at end of file +!function(){"use strict";var e={274:function(e,t,n){var r,o=(r=n(521))&&r.__esModule?r:{default:r};document.addEventListener("DOMContentLoaded",(()=>{(0,o.default)()}))},521:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=u(n(648)),o=u(n(809)),l=u(n(852)),i=u(n(117)),a=u(n(606));function u(e){return e&&e.__esModule?e:{default:e}}var s=()=>{r.default.component.registerMany({LinkPicker:o.default,LinkField:l.default,"LinkModal.FormBuilderModal":i.default,"LinkModal.InsertMediaModal":a.default})};t.default=s},852:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=_(n(363)),o=n(827),l=n(624),i=(n(648),m(n(42))),a=m(n(809)),u=m(n(734)),s=m(n(686)),d=m(n(697)),f=_(n(123)),c=m(n(159)),p=m(n(510)),y=m(n(86)),v=m(n(754));function m(e){return e&&e.__esModule?e:{default:e}}function k(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(k=function(e){return e?n:t})(e)}function _(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=k(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var i=o?Object.getOwnPropertyDescriptor(e,l):null;i&&(i.get||i.set)?Object.defineProperty(r,l,i):r[l]=e[l]}return r.default=e,n&&n.set(e,r),r}const O="SilverStripe\\LinkField\\Controllers\\LinkFieldController",h=e=>{var t;let{value:n=null,onChange:o,types:l,actions:i,isMulti:s=!1}=e;const[f,y]=(0,r.useState)({}),[m,k]=(0,r.useState)(0);let _=n;Array.isArray(_)||("number"==typeof _&&0!=_&&(_=[_]),_||(_=[])),(0,r.useEffect)((()=>{if(!m&&_.length>0){const e=[];for(const t of _)e.push(`itemIDs[]=${t}`);const t=`${p.default.getSection(O).form.linkForm.dataUrl}?${e.join("&")}`;c.default.get(t).then((e=>e.json())).then((e=>{y(e)}))}}),[m,n&&n.length]);const h=()=>{k(0)},g=e=>{k(0);const t=[..._];t.includes(e)||t.push(e),o(s?t:t[0]),i.toasts.success(v.default._t("LinkField.SAVE_SUCCESS","Saved link"))},b=e=>{const t=`${p.default.getSection(O).form.linkForm.deleteUrl}/${e}`;c.default.delete(t,{},{"X-SecurityID":p.default.get("SecurityID")}).then((()=>{i.toasts.success(v.default._t("LinkField.DELETE_SUCCESS","Deleted link"))})).catch((()=>{i.toasts.error(v.default._t("LinkField.DELETE_ERROR","Failed to delete link"))}));const n={...f};delete n[e],y(n),o(s?Object.keys(n):0)},M=s||0===Object.keys(f).length,j=Boolean(m);return r.default.createElement(r.default.Fragment,null,M&&r.default.createElement(a.default,{onModalSuccess:g,onModalClosed:h,types:l}),r.default.createElement("div",null," ",(()=>{const e=[];for(const a of _){var t,n,o,i;if(!f[a])continue;const s=l.hasOwnProperty(null===(t=f[a])||void 0===t?void 0:t.typeKey)?l[null===(n=f[a])||void 0===n?void 0:n.typeKey]:{};e.push(r.default.createElement(u.default,{key:a,id:a,title:null===(o=f[a])||void 0===o?void 0:o.Title,description:null===(i=f[a])||void 0===i?void 0:i.description,typeTitle:s.title||"",onClear:b,onClick:()=>{k(a)}}))}return e})()," "),j&&r.default.createElement(d.default,{types:l,typeKey:null===(t=f[m])||void 0===t?void 0:t.typeKey,isOpen:Boolean(m),onSuccess:g,onClosed:h,linkID:m}))};h.propTypes={value:y.default.oneOfType([y.default.arrayOf(y.default.number),y.default.number]),onChange:y.default.func.isRequired,types:y.default.objectOf(s.default).isRequired,actions:y.default.object.isRequired,isMulti:y.default.bool};var g=(0,o.compose)(i.default,(0,l.connect)(null,(e=>({actions:{toasts:(0,o.bindActionCreators)(f,e)}}))))(h);t.default=g},606:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;s(n(754));var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=u(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var i=o?Object.getOwnPropertyDescriptor(e,l):null;i&&(i.get||i.set)?Object.defineProperty(r,l,i):r[l]=e[l]}r.default=e,n&&n.set(e,r);return r}(n(363)),o=s(n(475)),l=n(624),i=s(n(686)),a=s(n(86));function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(u=function(e){return e?n:t})(e)}function s(e){return e&&e.__esModule?e:{default:e}}function d(){return d=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let{type:t,editing:n,data:l,actions:i,onSubmit:a,...u}=e;if(!t)return!1;(0,r.useEffect)((()=>{n?i.initModal():i.reset()}),[n]);const s=l?{ID:l.FileID,Description:l.Title,TargetBlank:!!l.OpenInNew}:{};return r.default.createElement(o.default,d({isOpen:n,type:"insert-link",title:!1,bodyClassName:"modal__dialog",className:"insert-link__dialog-wrapper--internal",fileAttributes:s,onInsert:e=>{let{ID:n,Description:r,TargetBlank:o}=e;return a({FileID:n,Title:r,OpenInNew:o,typeKey:t.key},"",(()=>{}))}},u))};f.propTypes={type:i.default.isRequired,editing:a.default.bool.isRequired,data:a.default.object.isRequired,actions:a.default.object.isRequired,onClick:a.default.func.isRequired};var c=(0,l.connect)((function(){return{}}),(function(e){return{actions:{initModal:()=>e({type:"INIT_FORM_SCHEMA_STACK",payload:{formSchema:{type:"insert-link",nextType:"admin"}}}),reset:()=>e({type:"RESET"})}}}))(f);t.default=c},117:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=s(n(363)),o=s(n(912)),l=s(n(872)),i=s(n(902)),a=s(n(510)),u=s(n(86));function s(e){return e&&e.__esModule?e:{default:e}}const d=(e,t)=>{const{schemaUrl:n}=a.default.getSection("SilverStripe\\LinkField\\Controllers\\LinkFieldController").form.linkForm,r=l.default.parse(n),o=i.default.parse(r.query);o.typeKey=e;for(const e of["href","path","pathname"])r[e]=`${r[e]}/${t}`;return l.default.format({...r,search:i.default.stringify(o)})},f=e=>{let{typeTitle:t,typeKey:n,linkID:l=0,isOpen:i,onSuccess:a,onClosed:u}=e;if(!n)return!1;return r.default.createElement(o.default,{title:t,isOpen:i,schemaUrl:d(n,l),identifier:"Link.EditingLinkInfo",onSubmit:async(e,t,n)=>{const r=await n();if(!r.id.match(/\/schema\/linkfield\/([0-9]+)/)){const e=r.id.match(/\/linkForm\/([0-9]+)/),t=parseInt(e[1],10);a(t)}return Promise.resolve()},onClosed:u})};f.propTypes={typeTitle:u.default.string.isRequired,typeKey:u.default.string.isRequired,linkID:u.default.number,isOpen:u.default.bool.isRequired,onSuccess:u.default.func.isRequired,onClosed:u.default.func.isRequired};var c=f;t.default=c},809:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.Component=void 0;var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=d(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var i=o?Object.getOwnPropertyDescriptor(e,l):null;i&&(i.get||i.set)?Object.defineProperty(r,l,i):r[l]=e[l]}r.default=e,n&&n.set(e,r);return r}(n(363)),o=s(n(86)),l=s(n(820)),i=s(n(97)),a=s(n(686)),u=s(n(697));function s(e){return e&&e.__esModule?e:{default:e}}function d(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(d=function(e){return e?n:t})(e)}const f=e=>{let{types:t,onModalSuccess:n,onModalClosed:o}=e;const[a,s]=(0,r.useState)(""),d=""!==a,f=(0,l.default)("link-picker","form-control"),c=Object.values(t);return r.default.createElement("div",{className:f},r.default.createElement(i.default,{types:c,onSelect:e=>{s(e)}}),d&&r.default.createElement(u.default,{types:t,typeKey:a,isOpen:d,onSuccess:e=>{s(""),n(e)},onClosed:()=>{"function"==typeof o&&o(),s("")}}))};t.Component=f,f.propTypes={types:o.default.objectOf(a.default).isRequired,onModalSuccess:o.default.func.isRequired,onModalClosed:o.default.func};var c=f;t.default=c},97:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=s(n(754)),o=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=u(t);if(n&&n.has(e))return n.get(e);var r={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var i=o?Object.getOwnPropertyDescriptor(e,l):null;i&&(i.get||i.set)?Object.defineProperty(r,l,i):r[l]=e[l]}r.default=e,n&&n.set(e,r);return r}(n(363)),l=s(n(86)),i=n(127),a=s(n(686));function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(u=function(e){return e?n:t})(e)}function s(e){return e&&e.__esModule?e:{default:e}}const d=e=>{let{types:t,onSelect:n}=e;const[l,a]=(0,o.useState)(!1);return o.default.createElement(i.Dropdown,{isOpen:l,toggle:()=>a((e=>!e)),className:"link-picker__menu"},o.default.createElement(i.DropdownToggle,{className:"link-picker__menu-toggle font-icon-plus-1",caret:!0},r.default._t("LinkField.ADD_LINK","Add Link")),o.default.createElement(i.DropdownMenu,null,t.map((e=>{let{key:t,title:r}=e;return o.default.createElement(i.DropdownItem,{key:t,onClick:()=>n(t)},r)}))))};d.propTypes={types:l.default.arrayOf(a.default).isRequired,onSelect:l.default.func.isRequired};var f=d;t.default=f},734:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=a(n(754)),o=a(n(363)),l=a(n(86)),i=n(127);function a(e){return e&&e.__esModule?e:{default:e}}const u=e=>t=>{t.nativeEvent.stopImmediatePropagation(),t.preventDefault(),t.nativeEvent.preventDefault(),t.stopPropagation(),e&&e()},s=e=>{let{id:t,title:n,description:l,typeTitle:a,onClear:s,onClick:d}=e;return o.default.createElement("div",{className:classnames("link-picker__link","form-control")},o.default.createElement(i.Button,{className:"link-picker__button font-icon-link",color:"secondary",onClick:u(d)},o.default.createElement("div",{className:"link-picker__link-detail"},o.default.createElement("div",{className:"link-picker__title"},n),o.default.createElement("small",{className:"link-picker__type"},a,": ",o.default.createElement("span",{className:"link-picker__url"},l)))),o.default.createElement(i.Button,{className:"link-picker__clear",color:"link",onClick:u((()=>s(t)))},r.default._t("LinkField.CLEAR","Clear")))};s.propTypes={id:l.default.number.isRequired,title:l.default.string,description:l.default.string,typeTitle:l.default.string.isRequired,onClear:l.default.func.isRequired,onClick:l.default.func.isRequired};var d=s;t.default=d},697:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=a(n(363)),o=n(648),l=a(n(86)),i=a(n(686));function a(e){return e&&e.__esModule?e:{default:e}}const u=e=>{let{types:t,typeKey:n,linkID:l=0,isOpen:i,onSuccess:a,onClosed:u}=e;if(!n)return!1;const s=t.hasOwnProperty(n)?t[n]:{},d=s&&s.hasOwnProperty("handlerName")?s.handlerName:"FormBuilderModal",f=(0,o.loadComponent)(`LinkModal.${d}`);return r.default.createElement(f,{typeTitle:s.title||"",typeKey:n,linkID:l,isOpen:i,onSuccess:a,onClosed:u})};u.propTypes={types:l.default.objectOf(i.default).isRequired,typeKey:l.default.string.isRequired,linkID:l.default.number,isOpen:l.default.bool.isRequired,onSuccess:l.default.func.isRequired,onClosed:l.default.func.isRequired};var s=u;t.default=s},41:function(e,t,n){var r=a(n(311)),o=a(n(363)),l=a(n(691)),i=n(648);function a(e){return e&&e.__esModule?e:{default:e}}function u(){return u=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e(".js-injector-boot .entwine-linkfield").entwine({Component:null,Root:null,onmatch(){const e=this.closest(".cms-content").attr("id"),t=e?{context:e}:{},n=this.data("schema-component"),r=(0,i.loadComponent)(n,t);this.setComponent(r),this.setRoot(l.default.createRoot(this[0])),this._super(),this.refresh()},refresh(){const e=this.getProps();this.getInputField().val(e.value);const t=this.getComponent();this.getRoot().render(o.default.createElement(t,u({},e,{noHolder:!0})))},handleChange(e){this.getInputField().data("value",e),this.refresh()},getProps(){return{value:this.getInputField().data("value"),onChange:this.handleChange.bind(this),isMulti:this.data("is-multi")??!1,types:this.data("types")??[]}},getInputField(){const t=this.data("field-id");return e(`#${t}`)},onunmatch(){const e=this.getRoot();e&&e.unmount()}})}))},686:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,o=(r=n(86))&&r.__esModule?r:{default:r};var l=o.default.shape({key:o.default.string.isRequired,title:o.default.string.isRequired});t.default=l},159:function(e){e.exports=Backend},510:function(e){e.exports=Config},42:function(e){e.exports=FieldHolder},912:function(e){e.exports=FormBuilderModal},648:function(e){e.exports=Injector},475:function(e){e.exports=InsertMediaModal},872:function(e){e.exports=NodeUrl},86:function(e){e.exports=PropTypes},363:function(e){e.exports=React},691:function(e){e.exports=ReactDomClient},624:function(e){e.exports=ReactRedux},127:function(e){e.exports=Reactstrap},827:function(e){e.exports=Redux},123:function(e){e.exports=ToastsActions},820:function(e){e.exports=classnames},754:function(e){e.exports=i18n},311:function(e){e.exports=jQuery},902:function(e){e.exports=qs}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var l=t[r]={exports:{}};return e[r](l,l.exports,n),l.exports}n(274),n(41)}(); \ No newline at end of file diff --git a/client/src/boot/index.js b/client/src/boot/index.js index 7fac11d0..3dbbb99b 100644 --- a/client/src/boot/index.js +++ b/client/src/boot/index.js @@ -1,9 +1,7 @@ /* global document */ /* eslint-disable */ import registerComponents from './registerComponents'; -import registerQueries from './registerQueries'; document.addEventListener('DOMContentLoaded', () => { registerComponents(); - registerQueries(); }); diff --git a/client/src/boot/registerQueries.js b/client/src/boot/registerQueries.js deleted file mode 100644 index 229ae7d9..00000000 --- a/client/src/boot/registerQueries.js +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable */ -import Injector from 'lib/Injector'; -import readLinkTypes from 'state/linkTypes/readLinkTypes'; - -const registerQueries = () => { - Injector.query.register('readLinkTypes', readLinkTypes); -}; -export default registerQueries; diff --git a/client/src/components/LinkField/LinkField.js b/client/src/components/LinkField/LinkField.js index 4cc94e6a..03f16f0c 100644 --- a/client/src/components/LinkField/LinkField.js +++ b/client/src/components/LinkField/LinkField.js @@ -183,7 +183,6 @@ const mapDispatchToProps = (dispatch) => ({ }); export default compose( - injectGraphql('readLinkTypes'), fieldHolder, connect(null, mapDispatchToProps) )(LinkField); diff --git a/client/src/entwine/LinkField.js b/client/src/entwine/LinkField.js index 0c8b789b..ccd45f9f 100644 --- a/client/src/entwine/LinkField.js +++ b/client/src/entwine/LinkField.js @@ -50,6 +50,7 @@ jQuery.entwine('ss', ($) => { value, onChange: this.handleChange.bind(this), isMulti: this.data('is-multi') ?? false, + types: this.data('types') ?? [], }; }, diff --git a/src/Controllers/LinkFieldController.php b/src/Controllers/LinkFieldController.php index 309d991f..4e943cac 100644 --- a/src/Controllers/LinkFieldController.php +++ b/src/Controllers/LinkFieldController.php @@ -8,7 +8,6 @@ use SilverStripe\Forms\DefaultFormFactory; use SilverStripe\Forms\Form; use SilverStripe\LinkField\Models\Link; -use SilverStripe\LinkField\Type\Registry; use SilverStripe\Security\SecurityToken; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormAction; @@ -19,6 +18,8 @@ use SilverStripe\Control\HTTPRequest; use SilverStripe\Core\Injector\Injector; use SilverStripe\Forms\HiddenField; +use SilverStripe\LinkField\Form\LinkField; +use SilverStripe\LinkField\Services\LinkTypeService; use SilverStripe\ORM\DataList; class LinkFieldController extends LeftAndMain @@ -74,7 +75,7 @@ public function linkForm(): Form } } else { $typeKey = $this->typeKeyFromRequest(); - $link = Registry::create()->byKey($typeKey); + $link = LinkTypeService::byKey($typeKey); if (!$link) { $this->jsonError(404, _t('LinkField.INVALID_TYPEKEY', 'Invalid typeKey')); } @@ -174,7 +175,7 @@ public function save(array $data, Form $form): HTTPResponse // Creating a new Link $operation = 'create'; $typeKey = $this->typeKeyFromRequest(); - $className = Registry::create()->list()[$typeKey] ?? ''; + $className = LinkTypeService::byKey($typeKey) ?? ''; if (!$className) { $this->jsonError(404, _t('LinkField.INVALID_TYPEKEY', 'Invalid typeKey')); } @@ -239,7 +240,7 @@ private function createLinkForm(Link $link, string $operation): Form $form = $formFactory->getForm($this, $name, ['Record' => $link]); // Set where the form is submitted to - $typeKey = Registry::create()->keyByClassName($link->ClassName); + $typeKey = LinkTypeService::keyByClassName($link->ClassName); $form->setFormAction($this->Link("linkForm/$id?typeKey=$typeKey")); // Add save action button diff --git a/src/Form/LinkField.php b/src/Form/LinkField.php index e31eab16..28f5c74d 100644 --- a/src/Form/LinkField.php +++ b/src/Form/LinkField.php @@ -7,12 +7,15 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObjectInterface; use SilverStripe\LinkField\Models\Link; +use SilverStripe\LinkField\Traits\AllowedLinkClassesTrait; /** * Allows CMS users to edit a Link object. */ class LinkField extends FormField { + use AllowedLinkClassesTrait; + protected $schemaComponent = 'LinkField'; protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_CUSTOM; diff --git a/src/Form/MultiLinkField.php b/src/Form/MultiLinkField.php index f4874b95..bdc9c0ad 100644 --- a/src/Form/MultiLinkField.php +++ b/src/Form/MultiLinkField.php @@ -4,6 +4,7 @@ use LogicException; use SilverStripe\Forms\FormField; +use SilverStripe\LinkField\Traits\AllowedLinkClassesTrait; use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\Relation; @@ -16,6 +17,8 @@ */ class MultiLinkField extends FormField { + use AllowedLinkClassesTrait; + protected $schemaComponent = 'LinkField'; protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_CUSTOM; diff --git a/src/GraphQL/LinkTypeResolver.php b/src/GraphQL/LinkTypeResolver.php deleted file mode 100644 index b03fe343..00000000 --- a/src/GraphQL/LinkTypeResolver.php +++ /dev/null @@ -1,30 +0,0 @@ -list(); - $flattenType = array_map(function (Link $type, string $key) { - return [ - 'key' => $key, - 'title' => $type->LinkTypeTile(), - 'handlerName' => $type->LinkTypeHandlerName(), - ]; - }, $types, array_keys($types)); - - return $flattenType; - } -} diff --git a/src/Models/Link.php b/src/Models/Link.php index cba2a807..0510d761 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -11,7 +11,8 @@ use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\RequiredFields; -use SilverStripe\LinkField\Type\Registry; +use SilverStripe\LinkField\Form\LinkField; +use SilverStripe\LinkField\Services\LinkTypeService; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\FieldType\DBHTMLText; @@ -188,7 +189,7 @@ function setData($data): Link ); } - $type = Registry::singleton()->byKey($typeKey); + $type = LinkTypeService::byKey($typeKey); if (!$type) { throw new InvalidArgumentException( @@ -225,7 +226,7 @@ function setData($data): Link public function jsonSerialize(): mixed { - $typeKey = Registry::singleton()->keyByClassName(static::class); + $typeKey = LinkTypeService::keyByClassName(static::class); if (!$typeKey) { return []; @@ -320,4 +321,9 @@ public function getDefaultTitle(): string } return $default; } + + public function getShortCode(): string + { + return strtolower(str_replace([' ', 'Link'], '', $this->LinkTypeTile())); + } } diff --git a/src/Services/LinkTypeService.php b/src/Services/LinkTypeService.php new file mode 100644 index 00000000..de5a0f3c --- /dev/null +++ b/src/Services/LinkTypeService.php @@ -0,0 +1,101 @@ +get($class)->getShortCode(); + $result[$type] = $class; + } + } + + return $result; + } + + /** + * Return a Link instance by key + * @throws InvalidArgumentException + */ + public static function byKey(string $key): ?Link + { + $typeDefinitions = static::generateAllLinkTypes(); + $definition = $typeDefinitions[$key] ?? null; + + if (!$definition) { + return null; + } + + return static::definitionToType($definition); + } + + /** + * Return a key for link type by classname + * @throws InvalidArgumentException + */ + public static function keyByClassName(string $classname): ?string + { + $typeDefinitions = static::generateAllLinkTypes(); + + foreach ($typeDefinitions as $key => $class) { + if ($class === $classname) { + return $key; + } + } + + return null; + } + + /** + * @throws InvalidArgumentException + */ + public static function definitionToType(string $className): Link + { + if (!$className && !($className instanceof Link)) { + throw new InvalidArgumentException( + _t( + 'LinkField.NO_CLASSNAME', + '"{class}": All types should reference a valid classname', + ['class' => static::class], + sprintf('%s: All types should reference a valid classname', static::class), + ), + ); + } + + /** @var Link $type */ + $type = Injector::inst()->get($className); + + if (!$type instanceof Link) { + throw new InvalidArgumentException( + _t( + 'LinkField.INVALID_TYPENAME', + '"{class}": {typename} is not a valid link type', + [ + 'class' => static::class, + 'typename' => $className, + ], + sprintf('%s: %s is not a valid link type', static::class, $className), + ), + ); + } + + return $type; + } +} diff --git a/src/Traits/AllowedLinkClassesTrait.php b/src/Traits/AllowedLinkClassesTrait.php new file mode 100644 index 00000000..1984083f --- /dev/null +++ b/src/Traits/AllowedLinkClassesTrait.php @@ -0,0 +1,117 @@ +validateTypes($types)) { + $this->allowed_types = $this->genarateAllowedTypes(); + } else { + $this->allowed_types = $this->genarateAllowedTypes($types); + } + + return $this; + } + + /** + * Validate types that they are subclasses of Link + */ + private function validateTypes(array $types): bool + { + $validClasses = []; + foreach ($types as $type) { + if (is_subclass_of($type, Link::class)) { + $validClasses[] = $type; + } + } + + return count($validClasses) > 0; + } + + /** + * Set disabled types for LinkField + */ + public function setDisabledTypes(array $types): static + { + if (is_array($types) || !empty($types)) { + $this->allowed_types = array_diff($this->genarateAllowedTypes(), $types); + } + + return $this; + } + + public function getAllowedTypes(): array + { + return $this->allowed_types; + } + + /** + * @throws InvalidArgumentException + */ + public function getTypesProps(): string + { + return json_encode($this->list()); + } + + /** + * @return Link[] + * @throws InvalidArgumentException + */ + public function list(): array + { + $types = []; + $typeDefinitions = $this->genarateAllowedTypes(); + + foreach ($typeDefinitions as $key => $class) { + $types[$key] = LinkTypeService::definitionToType($class); + } + + $newArray = []; + + array_map(function (Link $type, string $key) use (&$newArray) { + $newArray[$key] = [ + 'key' => $key, + 'title' => $type->LinkTypeTile(), + 'handlerName' => $type->LinkTypeHandlerName(), + ]; + }, $types, array_keys($types)); + + return $newArray; + } + + /** + * Generate allowed types with key => value pair + * Example: ['cms' => SiteTreeLink::class] + */ + private function genarateAllowedTypes(array $types = []): array + { + $typeDefinitions = !empty($types) ? $types : $this->getAllowedTypes(); + + if (empty($typeDefinitions)) { + $typeDefinitions = ClassInfo::subclassesFor(Link::class); + } + + $result = array(); + foreach ($typeDefinitions as $class) { + if (is_subclass_of($class, Link::class)) { + $type = Injector::inst()->get($class)->getShortCode(); + $result[$type] = $class; + } + } + + return $result; + } +} diff --git a/src/Type/Registry.php b/src/Type/Registry.php deleted file mode 100644 index 2f94a7dc..00000000 --- a/src/Type/Registry.php +++ /dev/null @@ -1,141 +0,0 @@ -get('types'); - $definition = $typeDefinitions[$key] ?? null; - - if (!$definition) { - return null; - } - - return $this->definitionToType($definition); - } - - /** - * @return Link[] - * @throws InvalidArgumentException - */ - public function list(): array - { - /** @var Link[] $types */ - $types = []; - - /** @var array $types */ - $typeDefinitions = self::config()->get('types'); - - foreach ($typeDefinitions as $key => $def) { - // This link type is disabled, so we can skip it - if (!array_key_exists('enabled', $def) || !$def['enabled']) { - continue; - } - - $types[$key] = $this->definitionToType($def); - } - - return $types; - } - - /** - * @return string[] - */ - public function keys(): array - { - return []; - } - - /** - * @return string[] - */ - public function keysEnabledByDefault(): array - { - return []; - } - - public function init() - { - foreach ($this->list() as $type) { - $type->defineLinkTypeRequirements(); - } - } - - /** - * @param array $def - * @throws LogicException - */ - private function definitionToType(array $def): Link - { - $className = $def['classname'] ?? null; - - if (!$className) { - throw new LogicException( - _t( - 'LinkField.NO_CLASSNAME', - '"{class}": All types should reference a valid classname', - ['class' => static::class], - sprintf('%s: All types should reference a valid classname', static::class), - ), - ); - } - - /** @var Link $type */ - $type = Injector::inst()->get($className); - - if (!$type instanceof Link) { - throw new LogicException( - _t( - 'LinkField.INVALID_TYPENAME', - '"{class}": {typename} is not a valid link type', - [ - 'class' => static::class, - 'typename' => $className, - ], - sprintf('%s: %s is not a valid link type', static::class, $className), - ), - ); - } - - return $type; - } - - public function keyByClassName(string $classname): ?string - { - $typeDefinitions = self::config()->get('types'); - - foreach ($typeDefinitions as $key => $def) { - if ($def['classname'] === $classname) { - return $key; - } - } - - return null; - } -} diff --git a/templates/SilverStripe/LinkField/Form/LinkField.ss b/templates/SilverStripe/LinkField/Form/LinkField.ss index c63fb16a..1710aab3 100644 --- a/templates/SilverStripe/LinkField/Form/LinkField.ss +++ b/templates/SilverStripe/LinkField/Form/LinkField.ss @@ -1,2 +1,2 @@ -
+
\ No newline at end of file diff --git a/templates/SilverStripe/LinkField/Form/MultiLinkField.ss b/templates/SilverStripe/LinkField/Form/MultiLinkField.ss index 6a3ef5d7..2103cefe 100644 --- a/templates/SilverStripe/LinkField/Form/MultiLinkField.ss +++ b/templates/SilverStripe/LinkField/Form/MultiLinkField.ss @@ -1,2 +1,2 @@ -
+
diff --git a/tests/php/Controllers/LinkFieldControllerTest.php b/tests/php/Controllers/LinkFieldControllerTest.php index 016be26d..ddbbf006 100644 --- a/tests/php/Controllers/LinkFieldControllerTest.php +++ b/tests/php/Controllers/LinkFieldControllerTest.php @@ -4,7 +4,6 @@ use SilverStripe\Dev\FunctionalTest; use SilverStripe\LinkField\Tests\Controllers\LinkFieldControllerTest\TestPhoneLink; -use SilverStripe\LinkField\Type\Registry; use SilverStripe\Core\Config\Config; use SilverStripe\Security\SecurityToken; @@ -21,12 +20,6 @@ class LinkFieldControllerTest extends FunctionalTest protected function setUp(): void { parent::setUp(); - $types = Config::inst()->get(Registry::class, 'types'); - $types['testphone'] = [ - 'classname' => TestPhoneLink::class, - 'enabled' => true, - ]; - Config::modify()->set(Registry::class, 'types', $types); $this->logInWithPermission('ADMIN'); // CSRF token check is normally disabled for unit-tests $this->securityTokenWasEnabled = SecurityToken::is_enabled(); @@ -39,9 +32,6 @@ protected function setUp(): void protected function tearDown(): void { parent::tearDown(); - $types = Config::inst()->get(Registry::class, 'types'); - unset($types['testphone']); - Config::modify()->set(Registry::class, 'types', $types); if (!$this->securityTokenWasEnabled) { SecurityToken::disable(); } diff --git a/tests/php/Models/LinkTest.php b/tests/php/Models/LinkTest.php index ad02ef3d..02c37b8d 100644 --- a/tests/php/Models/LinkTest.php +++ b/tests/php/Models/LinkTest.php @@ -16,11 +16,13 @@ use SilverStripe\LinkField\Models\Link; use SilverStripe\LinkField\Models\PhoneLink; use SilverStripe\LinkField\Models\SiteTreeLink; -use SilverStripe\LinkField\Type\Registry; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ValidationException; use SilverStripe\Versioned\Versioned; use SilverStripe\LinkField\Tests\Extensions\ExternalLinkExtension; +use SilverStripe\LinkField\Services\LinkTypeService; +use SilverStripe\Core\Injector\Injector; +use SilverStripe\LinkField\Tests\Controllers\LinkFieldControllerTest\TestPhoneLink; class LinkTest extends SapphireTest { @@ -119,125 +121,7 @@ public function linkTypeProvider(): array [FileLink::class, false], [PhoneLink::class, false], [SiteTreeLink::class, false], - [Link::class, true], - ]; - } - - /** - * @param array $types - * @param array $expected - * @return void - * @dataProvider linkTypeEnabledProvider - */ - public function testLinkTypeEnabled(array $types, array $expected): void - { - Config::withConfig(function (MutableConfigCollectionInterface $config) use ($types, $expected): void { - $config->set(Registry::class, 'types', $types); - - $enabledTypes = Registry::singleton()->list(); - $enabledTypes = array_map(static function (Link $link): string { - return $link->LinkTypeTile(); - }, $enabledTypes); - $enabledTypes = array_values($enabledTypes); - sort($enabledTypes, SORT_STRING); - - $this->assertSame($expected, $enabledTypes, 'We expect specific enabled link types'); - }); - } - - public function linkTypeEnabledProvider(): array - { - return [ - 'all types enabled' => [ - [ - 'cms' => [ - 'classname' => SiteTreeLink::class, - 'enabled' => true, - ], - 'external' => [ - 'classname' => ExternalLink::class, - 'enabled' => true, - ], - 'file' => [ - 'classname' => FileLink::class, - 'enabled' => true, - ], - 'email' => [ - 'classname' => EmailLink::class, - 'enabled' => true, - ], - 'phone' => [ - 'classname' => PhoneLink::class, - 'enabled' => true, - ], - ], - [ - 'Email Link', - 'External Link', - 'File Link', - 'Phone Link', - 'Site Tree Link', - ], - ], - 'file type disabled' => [ - [ - 'cms' => [ - 'classname' => SiteTreeLink::class, - 'enabled' => true, - ], - 'external' => [ - 'classname' => ExternalLink::class, - 'enabled' => true, - ], - 'file' => [ - 'classname' => FileLink::class, - 'enabled' => false, - ], - 'email' => [ - 'classname' => EmailLink::class, - 'enabled' => true, - ], - 'phone' => [ - 'classname' => PhoneLink::class, - 'enabled' => true, - ], - ], - [ - 'Email Link', - 'External Link', - 'Phone Link', - 'Site Tree Link', - ], - ], - 'phone and email types disabled' => [ - [ - 'cms' => [ - 'classname' => SiteTreeLink::class, - 'enabled' => true, - ], - 'external' => [ - 'classname' => ExternalLink::class, - 'enabled' => true, - ], - 'file' => [ - 'classname' => FileLink::class, - 'enabled' => true, - ], - 'email' => [ - 'classname' => EmailLink::class, - 'enabled' => false, - ], - 'phone' => [ - 'classname' => PhoneLink::class, - 'enabled' => false, - ], - ], - [ - 'External Link', - 'File Link', - 'Site Tree Link', - ], - ], + [TestPhoneLink::class, false], ]; } diff --git a/tests/php/Traits/AllowedLinkClassesTraitTest.php b/tests/php/Traits/AllowedLinkClassesTraitTest.php new file mode 100644 index 00000000..d493ec2a --- /dev/null +++ b/tests/php/Traits/AllowedLinkClassesTraitTest.php @@ -0,0 +1,179 @@ +setAllowedTypes($enabled); + $this->assertEquals($expected, $trait->getAllowedTypes()); + } + + public function allowedTypesDataProvider() : array + { + return [ + 'allow all Link classes' => [ + 'enabled' => [ + SiteTreeLink::class, + ExternalLink::class, + FileLink::class, + EmailLink::class, + PhoneLink::class, + TestPhoneLink::class, + ], + 'expected' => [ + 'sitetree' => SiteTreeLink::class, + 'external' => ExternalLink::class, + 'file' => FileLink::class, + 'email' => EmailLink::class, + 'phone' => PhoneLink::class, + 'testphone' => TestPhoneLink::class, + ], + ], + 'allow only SiteTreeLink class' => [ + 'enabled' => [SiteTreeLink::class], + 'expected' => ['sitetree' => SiteTreeLink::class], + ], + 'allow all with empty array' => [ + 'enabled' => [], + 'expected' => [ + 'sitetree' => SiteTreeLink::class, + 'external' => ExternalLink::class, + 'file' => FileLink::class, + 'email' => EmailLink::class, + 'phone' => PhoneLink::class, + 'testphone' => TestPhoneLink::class, + ], + ], + 'all all non-Link classes' => [ + 'enabled' => [DataObject::class, 'WrongClass', 1, true, ], + 'expected' => [ + 'sitetree' => SiteTreeLink::class, + 'external' => ExternalLink::class, + 'file' => FileLink::class, + 'email' => EmailLink::class, + 'phone' => PhoneLink::class, + 'testphone' => TestPhoneLink::class, + ], + ], + 'allow one PhoneLink and few non-Link classes' => [ + 'enabled' => [PhoneLink::class, 'WrongClass', 1, true, ], + 'expected' => [ + 'phone' => PhoneLink::class + ], + ], + ]; + } + + /** + * @dataProvider disabledTypesDataProvider + */ + public function testSetDisabledTypes(array $disabled, array $expected): void + { + $trait = LinkField::create('Test'); + $trait->setDisabledTypes($disabled); + $this->assertEquals($expected, $trait->getAllowedTypes()); + } + + public function disabledTypesDataProvider() : array + { + return [ + 'disable all Link classes' => [ + 'disabled' => [ + SiteTreeLink::class, + ExternalLink::class, + FileLink::class, + EmailLink::class, + PhoneLink::class, + TestPhoneLink::class, + ], + 'expected' => [], + ], + 'disable only SiteTreeLink class' => [ + 'disabled' => [SiteTreeLink::class], + 'expected' => [ + 'external' => ExternalLink::class, + 'file' => FileLink::class, + 'email' => EmailLink::class, + 'phone' => PhoneLink::class, + 'testphone' => TestPhoneLink::class, + ], + ], + 'disable all with empty array' => [ + 'disabled' => [], + 'expected' => [ + 'sitetree' => SiteTreeLink::class, + 'external' => ExternalLink::class, + 'file' => FileLink::class, + 'email' => EmailLink::class, + 'phone' => PhoneLink::class, + 'testphone' => TestPhoneLink::class, + ], + ], + 'disable all non-Link classes' => [ + 'disabled' => [DataObject::class, 'WrongClass', 1, true, ], + 'expected' => [ + 'sitetree' => SiteTreeLink::class, + 'external' => ExternalLink::class, + 'file' => FileLink::class, + 'email' => EmailLink::class, + 'phone' => PhoneLink::class, + 'testphone' => TestPhoneLink::class, + ], + ], + 'disable one PhoneLink and few non-Link classes' => [ + 'disabled' => [PhoneLink::class, 'WrongClass', 1, true, ], + 'expected' => [ + 'sitetree' => SiteTreeLink::class, + 'external' => ExternalLink::class, + 'file' => FileLink::class, + 'email' => EmailLink::class, + 'testphone' => TestPhoneLink::class, + ], + ], + ]; + } + + public function testList(): void + { + $trait = LinkField::create('Test'); + $trait->setDisabledTypes([SiteTreeLink::class, ExternalLink::class, FileLink::class]); + $expected = [ + "email" => [ + "key" => "email", + "title" => "Email Link", + "handlerName" => "FormBuilderModal", + ], + "phone" => [ + "key" => "phone", + "title" => "Phone Link", + "handlerName" => "FormBuilderModal", + ], + "testphone" => [ + "key" => "testphone", + "title" => "Test Phone Link", + "handlerName" => "FormBuilderModal", + ], + ]; + $this->assertEquals($expected, $trait->list()); + } +}