mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Updates to Form, ValidationResponse, ValidationException
API Implement form schema "errors" handling
This commit is contained in:
parent
6650561dac
commit
6e589aac75
189
admin/client/dist/js/bundle.js
vendored
189
admin/client/dist/js/bundle.js
vendored
@ -1,6 +1,6 @@
|
|||||||
webpackJsonp([5],[function(e,t,n){"use strict"
|
webpackJsonp([5],[function(e,t,n){"use strict"
|
||||||
n(2),n(3),n(6),n(19),n(25),n(27),n(29),n(31),n(34),n(105),n(112),n(116),n(126),n(127),n(128),n(129),n(130),n(131),n(133),n(136),n(138),n(141),n(144),n(146),n(148),n(150),n(151),n(160),n(161),n(163),n(164),
|
n(2),n(3),n(6),n(19),n(25),n(27),n(29),n(31),n(34),n(105),n(113),n(117),n(127),n(128),n(129),n(130),n(131),n(132),n(134),n(137),n(139),n(142),n(145),n(147),n(149),n(151),n(152),n(161),n(162),n(164),n(165),
|
||||||
n(165),n(166),n(167),n(168),n(169),n(170),n(171),n(172),n(173),n(174),n(175),n(178),n(180),n(181),n(182),n(183),n(187),n(188),n(189),n(190),n(191),n(188),n(183),n(194),n(195),n(197),n(198)},,function(e,t){
|
n(166),n(167),n(168),n(169),n(170),n(171),n(172),n(173),n(174),n(175),n(176),n(179),n(181),n(182),n(183),n(184),n(188),n(189),n(190),n(191),n(192),n(189),n(184),n(195),n(196),n(198),n(199)},,function(e,t){
|
||||||
"use strict"
|
"use strict"
|
||||||
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
@ -430,10 +430,10 @@ return Object.entries(e).reduce(function(e,n){var o=u(n,1),a=o[0],s=(0,m.findFie
|
|||||||
if(d)return e
|
if(d)return e
|
||||||
var h=p.map(function(e,t){return f["default"].createElement("span",{key:t,className:"form__validation-message"},e)})
|
var h=p.map(function(e,t){return f["default"].createElement("span",{key:t,className:"form__validation-message"},e)})
|
||||||
return l({},e,r({},a,{type:"error",value:{react:h}}))},{})}},{key:"handleAction",value:function i(e){"function"==typeof this.props.handleAction&&this.props.handleAction(e,this.props.values),e.isPropagationStopped()||this.setState({
|
return l({},e,r({},a,{type:"error",value:{react:h}}))},{})}},{key:"handleAction",value:function i(e){"function"==typeof this.props.handleAction&&this.props.handleAction(e,this.props.values),e.isPropagationStopped()||this.setState({
|
||||||
submittingAction:e.currentTarget.name})}},{key:"handleSubmit",value:function d(e){var t=this,n=this.state.submittingAction?this.state.submittingAction:this.props.schema.schema.actions[0].name,i=l({},e,r({},n,1)),o={
|
submittingAction:e.currentTarget.name})}},{key:"handleSubmit",value:function d(e){var t=this,n=this.state.submittingAction?this.state.submittingAction:this.props.schema.schema.actions[0].name,i=l({},e,r({},n,1)),o=this.props.responseRequestedSchema.join(),a={
|
||||||
"X-Formschema-Request":"state,schema","X-Requested-With":"XMLHttpRequest"},a=function s(e){return t.submitApi(e||i,o).then(function(e){return t.setState({submittingAction:null}),e})["catch"](function(e){
|
"X-Formschema-Request":o,"X-Requested-With":"XMLHttpRequest"},s=function u(e){return t.submitApi(e||i,a).then(function(e){return t.setState({submittingAction:null}),e})["catch"](function(e){throw t.setState({
|
||||||
throw t.setState({submittingAction:null}),e})}
|
submittingAction:null}),e})}
|
||||||
return"function"==typeof this.props.handleSubmit?this.props.handleSubmit(i,n,a):a()}},{key:"buildComponent",value:function p(e){var t=e,n=null!==t.schemaComponent?E["default"].getComponentByName(t.schemaComponent):E["default"].getComponentByDataType(t.type)
|
return"function"==typeof this.props.handleSubmit?this.props.handleSubmit(i,n,s):s()}},{key:"buildComponent",value:function p(e){var t=e,n=null!==t.schemaComponent?E["default"].getComponentByName(t.schemaComponent):E["default"].getComponentByDataType(t.type)
|
||||||
|
|
||||||
|
|
||||||
if(null===n)return null
|
if(null===n)return null
|
||||||
@ -461,9 +461,11 @@ persistentSubmitErrors:p,validate:this.validateForm}
|
|||||||
return f["default"].createElement(n,v)}}]),t}(y["default"]),O=d.PropTypes.shape({id:d.PropTypes.string,schema:d.PropTypes.shape({attributes:d.PropTypes.shape({"class":d.PropTypes.string,enctype:d.PropTypes.string
|
return f["default"].createElement(n,v)}}]),t}(y["default"]),O=d.PropTypes.shape({id:d.PropTypes.string,schema:d.PropTypes.shape({attributes:d.PropTypes.shape({"class":d.PropTypes.string,enctype:d.PropTypes.string
|
||||||
}),fields:d.PropTypes.array.isRequired}),state:d.PropTypes.shape({fields:d.PropTypes.array}),loading:d.PropTypes["boolean"],stateOverride:d.PropTypes.shape({fields:d.PropTypes.array})}),S={createFn:d.PropTypes.func,
|
}),fields:d.PropTypes.array.isRequired}),state:d.PropTypes.shape({fields:d.PropTypes.array}),loading:d.PropTypes["boolean"],stateOverride:d.PropTypes.shape({fields:d.PropTypes.array})}),S={createFn:d.PropTypes.func,
|
||||||
handleSubmit:d.PropTypes.func,handleAction:d.PropTypes.func,asyncValidate:d.PropTypes.func,onSubmitFail:d.PropTypes.func,onSubmitSuccess:d.PropTypes.func,shouldAsyncValidate:d.PropTypes.func,touchOnBlur:d.PropTypes.bool,
|
handleSubmit:d.PropTypes.func,handleAction:d.PropTypes.func,asyncValidate:d.PropTypes.func,onSubmitFail:d.PropTypes.func,onSubmitSuccess:d.PropTypes.func,shouldAsyncValidate:d.PropTypes.func,touchOnBlur:d.PropTypes.bool,
|
||||||
touchOnChange:d.PropTypes.bool,persistentSubmitErrors:d.PropTypes.bool,validate:d.PropTypes.func,values:d.PropTypes.object,submitting:d.PropTypes.bool,baseFormComponent:d.PropTypes.func.isRequired,baseFieldComponent:d.PropTypes.func.isRequired
|
touchOnChange:d.PropTypes.bool,persistentSubmitErrors:d.PropTypes.bool,validate:d.PropTypes.func,values:d.PropTypes.object,submitting:d.PropTypes.bool,baseFormComponent:d.PropTypes.func.isRequired,baseFieldComponent:d.PropTypes.func.isRequired,
|
||||||
}
|
responseRequestedSchema:d.PropTypes.arrayOf(d.PropTypes.oneOf(["schema","state","errors","auto"]))}
|
||||||
P.propTypes=l({},S,{form:d.PropTypes.string.isRequired,schema:O.isRequired}),t.basePropTypes=S,t.schemaPropType=O,t["default"]=P},function(e,t){"use strict"
|
P.propTypes=l({},S,{form:d.PropTypes.string.isRequired,schema:O.isRequired}),P.defaultProps={responseRequestedSchema:["auto"]},t.basePropTypes=S,t.schemaPropType=O,t["default"]=P},function(e,t){"use strict"
|
||||||
|
|
||||||
|
|
||||||
function n(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=null
|
function n(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=null
|
||||||
if(!e)return n
|
if(!e)return n
|
||||||
n=e.find(function(e){return e.name===t})
|
n=e.find(function(e){return e.name===t})
|
||||||
@ -542,39 +544,42 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t){var n=e.schemas[t.schemaUrl],i=e.form&&e.form[t.schemaUrl],r=i&&i.submitting,o=i&&i.values,a=n&&n.stateOverride,s=n&&n.metadata&&n.metadata.loading
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t){var n=e.schemas[t.schemaUrl],i=e.form&&e.form[t.schemaUrl],r=i&&i.submitting,o=i&&i.values,a=n&&n.stateOverride,s=n&&n.metadata&&n.metadata.loading
|
||||||
|
|
||||||
|
|
||||||
return{schema:n,submitting:r,values:o,stateOverrides:a,loading:s}}function u(e){return{schemaActions:(0,m.bindActionCreators)(_,e)}}Object.defineProperty(t,"__esModule",{value:!0})
|
return{schema:n,submitting:r,values:o,stateOverrides:a,loading:s}}function u(e){return{schemaActions:(0,m.bindActionCreators)(C,e)}}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},d=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},d=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),f=n(5),p=r(f),h=n(107),m=n(108),g=n(8),v=r(g),y=n(109),b=n(110),_=i(b),w=n(17),C=r(w),T=n(26),E=r(T),P=n(111),O=r(P),S=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),f=n(5),p=r(f),h=n(107),m=n(108),g=n(8),v=r(g),y=n(109),b=r(y),_=n(110),w=n(111),C=i(w),T=n(17),E=r(T),P=n(26),O=r(P),S=n(112),k=r(S),j=function(e){
|
||||||
function t(e){o(this,t)
|
function t(e){o(this,t)
|
||||||
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.handleSubmit=n.handleSubmit.bind(n),n.clearSchema=n.clearSchema.bind(n),n}return s(t,e),d(t,[{key:"componentDidMount",value:function n(){this.fetch()}},{key:"componentDidUpdate",value:function i(e){
|
return n.handleSubmit=n.handleSubmit.bind(n),n.clearSchema=n.clearSchema.bind(n),n.reduceSchemaErrors=n.reduceSchemaErrors.bind(n),n}return s(t,e),d(t,[{key:"componentDidMount",value:function n(){this.fetch()
|
||||||
this.props.schemaUrl!==e.schemaUrl&&(this.clearSchema(e.schemaUrl),this.fetch())}},{key:"componentWillUnmount",value:function r(){this.clearSchema(this.props.schemaUrl)}},{key:"getMessages",value:function l(e){
|
|
||||||
var t={}
|
}},{key:"componentDidUpdate",value:function i(e){this.props.schemaUrl!==e.schemaUrl&&(this.clearSchema(e.schemaUrl),this.fetch())}},{key:"componentWillUnmount",value:function r(){this.clearSchema(this.props.schemaUrl)
|
||||||
return e&&e.fields&&e.fields.forEach(function(e){e.message&&(t[e.name]=e.message)}),t}},{key:"clearSchema",value:function u(e){e&&((0,y.destroy)(e),this.props.schemaActions.setSchema(e,null))}},{key:"handleSubmit",
|
|
||||||
|
}},{key:"getMessages",value:function l(e){var t={}
|
||||||
|
return e&&e.fields&&e.fields.forEach(function(e){e.message&&(t[e.name]=e.message)}),t}},{key:"clearSchema",value:function u(e){e&&((0,_.destroy)(e),this.props.schemaActions.setSchema(e,null))}},{key:"handleSubmit",
|
||||||
value:function f(e,t,n){var i=this,r=null
|
value:function f(e,t,n){var i=this,r=null
|
||||||
if(r="function"==typeof this.props.handleSubmit?this.props.handleSubmit(e,t,n):n(),!r)throw new Error("Promise was not returned for submitting")
|
if(r="function"==typeof this.props.handleSubmit?this.props.handleSubmit(e,t,n):n(),!r)throw new Error("Promise was not returned for submitting")
|
||||||
return r.then(function(e){return e&&i.props.schemaActions.setSchema(i.props.schemaUrl,e),e}).then(function(e){if(!e||!e.state)return e
|
return r.then(function(e){var t=e
|
||||||
|
return t&&(t=i.reduceSchemaErrors(t),i.props.schemaActions.setSchema(i.props.schemaUrl,t)),t}).then(function(e){if(!e||!e.state)return e
|
||||||
var t=i.getMessages(e.state)
|
var t=i.getMessages(e.state)
|
||||||
if(Object.keys(t).length)throw new y.SubmissionError(t)
|
if(Object.keys(t).length)throw new _.SubmissionError(t)
|
||||||
return e})}},{key:"overrideStateData",value:function h(e){if(!this.props.stateOverrides||!e)return e
|
return e})}},{key:"reduceSchemaErrors",value:function h(e){if(!e.errors)return e
|
||||||
|
var t=c({},e)
|
||||||
|
return t.state||(t=c({},t,{state:this.props.schema.state})),t=c({},t,{state:c({},t.state,{fields:t.state.fields.map(function(t){return c({},t,{message:e.errors.find(function(e){return e.field===t.name})
|
||||||
|
})}),messages:e.errors.filter(function(e){return!e.field})})}),delete t.errors,(0,b["default"])(t)}},{key:"overrideStateData",value:function m(e){if(!this.props.stateOverrides||!e)return e
|
||||||
var t=this.props.stateOverrides.fields,n=e.fields
|
var t=this.props.stateOverrides.fields,n=e.fields
|
||||||
return t&&n&&(n=n.map(function(e){var n=t.find(function(t){return t.name===e.name})
|
return t&&n&&(n=n.map(function(e){var n=t.find(function(t){return t.name===e.name})
|
||||||
return n?C["default"].recursive(!0,e,n):e})),c({},e,this.props.stateOverrides,{fields:n})}},{key:"overrideStateData",value:function m(e){if(!this.props.stateOverrides||!e)return e
|
return n?E["default"].recursive(!0,e,n):e})),c({},e,this.props.stateOverrides,{fields:n})}},{key:"fetch",value:function g(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0],n=arguments.length<=1||void 0===arguments[1]||arguments[1],i=[]
|
||||||
var t=this.props.stateOverrides.fields,n=e.fields
|
|
||||||
return t&&n&&(n=n.map(function(e){var n=t.find(function(t){return t.name===e.name})
|
|
||||||
return n?C["default"].recursive(!0,e,n):e})),c({},e,this.props.stateOverrides,{fields:n})}},{key:"fetch",value:function g(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0],n=arguments.length<=1||void 0===arguments[1]||arguments[1],i=[]
|
|
||||||
|
|
||||||
|
|
||||||
return t&&i.push("schema"),n&&i.push("state"),this.props.loading?Promise.resolve({}):(this.props.schemaActions.setSchemaLoading(this.props.schemaUrl,!0),(0,v["default"])(this.props.schemaUrl,{headers:{
|
return t&&i.push("schema"),n&&i.push("state"),this.props.loading?Promise.resolve({}):(this.props.schemaActions.setSchemaLoading(this.props.schemaUrl,!0),(0,v["default"])(this.props.schemaUrl,{headers:{
|
||||||
"X-FormSchema-Request":i.join()},credentials:"same-origin"}).then(function(e){return e.json()}).then(function(t){if(e.props.schemaActions.setSchemaLoading(e.props.schemaUrl,!1),"undefined"!=typeof t.id){
|
"X-FormSchema-Request":i.join()},credentials:"same-origin"}).then(function(e){return e.json()}).then(function(t){if(e.props.schemaActions.setSchemaLoading(e.props.schemaUrl,!1),"undefined"!=typeof t.id){
|
||||||
var n=c({},t,{state:e.overrideStateData(t.state)})
|
var n=c({},t,{state:e.overrideStateData(t.state)})
|
||||||
return e.props.schemaActions.setSchema(e.props.schemaUrl,n),n}return t}))}},{key:"render",value:function b(){if(!this.props.schema||!this.props.schema.schema||this.props.loading)return null
|
return e.props.schemaActions.setSchema(e.props.schemaUrl,n),n}return t}))}},{key:"render",value:function y(){if(!this.props.schema||!this.props.schema.schema||this.props.loading)return null
|
||||||
var e=c({},this.props,{form:this.props.schemaUrl,onSubmitSuccess:this.props.onSubmitSuccess,handleSubmit:this.handleSubmit})
|
var e=c({},this.props,{form:this.props.schemaUrl,onSubmitSuccess:this.props.onSubmitSuccess,handleSubmit:this.handleSubmit})
|
||||||
return p["default"].createElement(O["default"],e)}}]),t}(f.Component)
|
return p["default"].createElement(k["default"],e)}}]),t}(f.Component)
|
||||||
S.propTypes=c({},P.basePropTypes,{schemaActions:f.PropTypes.object.isRequired,schemaUrl:f.PropTypes.string.isRequired,schema:P.schemaPropType,form:f.PropTypes.string,submitting:f.PropTypes.bool}),S.defaultProps={
|
j.propTypes=c({},S.basePropTypes,{schemaActions:f.PropTypes.object.isRequired,schemaUrl:f.PropTypes.string.isRequired,schema:S.schemaPropType,form:f.PropTypes.string,submitting:f.PropTypes.bool}),j.defaultProps={
|
||||||
baseFormComponent:(0,y.reduxForm)()(E["default"]),baseFieldComponent:y.Field},t["default"]=(0,h.connect)(l,u)(S)},,,function(e,t){e.exports=ReduxForm},function(e,t){e.exports=SchemaActions},function(e,t){
|
baseFormComponent:(0,_.reduxForm)()(O["default"]),baseFieldComponent:_.Field},t["default"]=(0,h.connect)(l,u)(j)},,,function(e,t){e.exports=DeepFreezeStrict},function(e,t){e.exports=ReduxForm},function(e,t){
|
||||||
e.exports=FormBuilder},function(e,t,n){(function(t){e.exports=t.FormBuilderModal=n(113)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
e.exports=SchemaActions},function(e,t){e.exports=FormBuilder},function(e,t,n){(function(t){e.exports=t.FormBuilderModal=n(114)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
||||||
|
|
||||||
|
|
||||||
@ -582,7 +587,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0})
|
value:!0})
|
||||||
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(114),d=i(c),f=n(22),p=n(21),h=i(p),m=n(115),g=i(m),v=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(115),d=i(c),f=n(22),p=n(21),h=i(p),m=n(116),g=i(m),v=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.handleSubmit=n.handleSubmit.bind(n),n.handleHide=n.handleHide.bind(n),n.clearResponse=n.clearResponse.bind(n),n}return a(t,e),s(t,[{key:"getForm",value:function n(){return this.props.schemaUrl?u["default"].createElement(g["default"],{
|
return n.handleSubmit=n.handleSubmit.bind(n),n.handleHide=n.handleHide.bind(n),n.clearResponse=n.clearResponse.bind(n),n}return a(t,e),s(t,[{key:"getForm",value:function n(){return this.props.schemaUrl?u["default"].createElement(g["default"],{
|
||||||
@ -603,7 +608,7 @@ className:this.props.bodyClassName},t,e,this.props.children))}}]),t}(h["default"
|
|||||||
v.propTypes={show:u["default"].PropTypes.bool,title:u["default"].PropTypes.oneOfType([u["default"].PropTypes.string,u["default"].PropTypes.bool]),className:u["default"].PropTypes.string,bodyClassName:u["default"].PropTypes.string,
|
v.propTypes={show:u["default"].PropTypes.bool,title:u["default"].PropTypes.oneOfType([u["default"].PropTypes.string,u["default"].PropTypes.bool]),className:u["default"].PropTypes.string,bodyClassName:u["default"].PropTypes.string,
|
||||||
handleHide:u["default"].PropTypes.func,schemaUrl:u["default"].PropTypes.string,handleSubmit:u["default"].PropTypes.func,handleAction:u["default"].PropTypes.func,responseClassGood:u["default"].PropTypes.string,
|
handleHide:u["default"].PropTypes.func,schemaUrl:u["default"].PropTypes.string,handleSubmit:u["default"].PropTypes.func,handleAction:u["default"].PropTypes.func,responseClassGood:u["default"].PropTypes.string,
|
||||||
responseClassBad:u["default"].PropTypes.string},v.defaultProps={show:!1,title:null},t["default"]=v},function(e,t){e.exports=i18n},function(e,t){e.exports=FormBuilderLoader},function(e,t,n){(function(t){
|
responseClassBad:u["default"].PropTypes.string},v.defaultProps={show:!1,title:null},t["default"]=v},function(e,t){e.exports=i18n},function(e,t){e.exports=FormBuilderLoader},function(e,t,n){(function(t){
|
||||||
e.exports=t.GridField=n(117)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
e.exports=t.GridField=n(118)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
||||||
function i(e){if(e&&e.__esModule)return e
|
function i(e){if(e&&e.__esModule)return e
|
||||||
var t={}
|
var t={}
|
||||||
if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])
|
if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])
|
||||||
@ -623,7 +628,7 @@ var i=Object.getOwnPropertyDescriptor(e,t)
|
|||||||
if(void 0===i){var r=Object.getPrototypeOf(e)
|
if(void 0===i){var r=Object.getPrototypeOf(e)
|
||||||
return null===r?void 0:M(r,t,n)}if("value"in i)return i.value
|
return null===r?void 0:M(r,t,n)}if("value"in i)return i.value
|
||||||
var o=i.get
|
var o=i.get
|
||||||
if(void 0!==o)return o.call(n)},f=n(5),p=r(f),h=n(108),m=n(107),g=n(21),v=r(g),y=n(118),b=r(y),_=n(119),w=r(_),C=n(121),T=r(C),E=n(120),P=r(E),O=n(122),S=r(O),k=n(123),j=r(k),x=n(28),R=r(x),I=n(124),A=i(I),D={},F=function(e){
|
if(void 0!==o)return o.call(n)},f=n(5),p=r(f),h=n(108),m=n(107),g=n(21),v=r(g),y=n(119),b=r(y),_=n(120),w=r(_),C=n(122),T=r(C),E=n(121),P=r(E),O=n(123),S=r(O),k=n(124),j=r(k),x=n(28),R=r(x),I=n(125),A=i(I),D={},F=function(e){
|
||||||
function t(e){o(this,t)
|
function t(e){o(this,t)
|
||||||
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.deleteRecord=n.deleteRecord.bind(n),n.editRecord=n.editRecord.bind(n),n}return s(t,e),c(t,[{key:"componentDidMount",value:function n(){d(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"componentDidMount",this).call(this)
|
return n.deleteRecord=n.deleteRecord.bind(n),n.editRecord=n.editRecord.bind(n),n}return s(t,e),c(t,[{key:"componentDidMount",value:function n(){d(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"componentDidMount",this).call(this)
|
||||||
@ -672,7 +677,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0})
|
value:!0})
|
||||||
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(120),p=i(f),h=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(121),p=i(f),h=function(e){
|
||||||
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),s(t,[{key:"render",value:function n(){return u["default"].createElement(p["default"],null,this.props.children)
|
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),s(t,[{key:"render",value:function n(){return u["default"].createElement(p["default"],null,this.props.children)
|
||||||
|
|
||||||
}}]),t}(d["default"])
|
}}]),t}(d["default"])
|
||||||
@ -745,13 +750,13 @@ type:u["default"].FETCH_RECORD_FAILURE,payload:{error:n,recordType:e}}),n})}}fun
|
|||||||
return function(n){return n({type:u["default"].DELETE_RECORD_REQUEST,payload:a}),d["default"][s].apply(d["default"],l).then(function(){n({type:u["default"].DELETE_RECORD_SUCCESS,payload:{recordType:e,id:t
|
return function(n){return n({type:u["default"].DELETE_RECORD_REQUEST,payload:a}),d["default"][s].apply(d["default"],l).then(function(){n({type:u["default"].DELETE_RECORD_SUCCESS,payload:{recordType:e,id:t
|
||||||
}})})["catch"](function(i){throw n({type:u["default"].DELETE_RECORD_FAILURE,payload:{error:i,recordType:e,id:t}}),i})}}Object.defineProperty(t,"__esModule",{value:!0}),t.fetchRecords=o,t.fetchRecord=a,
|
}})})["catch"](function(i){throw n({type:u["default"].DELETE_RECORD_FAILURE,payload:{error:i,recordType:e,id:t}}),i})}}Object.defineProperty(t,"__esModule",{value:!0}),t.fetchRecords=o,t.fetchRecord=a,
|
||||||
t.deleteRecord=s
|
t.deleteRecord=s
|
||||||
var l=n(125),u=i(l),c=n(7),d=i(c)},function(e,t){"use strict"
|
var l=n(126),u=i(l),c=n(7),d=i(c)},function(e,t){"use strict"
|
||||||
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={CREATE_RECORD:"CREATE_RECORD",UPDATE_RECORD:"UPDATE_RECORD",DELETE_RECORD:"DELETE_RECORD",FETCH_RECORDS_REQUEST:"FETCH_RECORDS_REQUEST",FETCH_RECORDS_FAILURE:"FETCH_RECORDS_FAILURE",
|
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={CREATE_RECORD:"CREATE_RECORD",UPDATE_RECORD:"UPDATE_RECORD",DELETE_RECORD:"DELETE_RECORD",FETCH_RECORDS_REQUEST:"FETCH_RECORDS_REQUEST",FETCH_RECORDS_FAILURE:"FETCH_RECORDS_FAILURE",
|
||||||
FETCH_RECORDS_SUCCESS:"FETCH_RECORDS_SUCCESS",FETCH_RECORD_REQUEST:"FETCH_RECORD_REQUEST",FETCH_RECORD_FAILURE:"FETCH_RECORD_FAILURE",FETCH_RECORD_SUCCESS:"FETCH_RECORD_SUCCESS",DELETE_RECORD_REQUEST:"DELETE_RECORD_REQUEST",
|
FETCH_RECORDS_SUCCESS:"FETCH_RECORDS_SUCCESS",FETCH_RECORD_REQUEST:"FETCH_RECORD_REQUEST",FETCH_RECORD_FAILURE:"FETCH_RECORD_FAILURE",FETCH_RECORD_SUCCESS:"FETCH_RECORD_SUCCESS",DELETE_RECORD_REQUEST:"DELETE_RECORD_REQUEST",
|
||||||
DELETE_RECORD_FAILURE:"DELETE_RECORD_FAILURE",DELETE_RECORD_SUCCESS:"DELETE_RECORD_SUCCESS"}},function(e,t,n){(function(t){e.exports=t.GridFieldCell=n(122)}).call(t,function(){return this}())},function(e,t,n){
|
DELETE_RECORD_FAILURE:"DELETE_RECORD_FAILURE",DELETE_RECORD_SUCCESS:"DELETE_RECORD_SUCCESS"}},function(e,t,n){(function(t){e.exports=t.GridFieldCell=n(123)}).call(t,function(){return this}())},function(e,t,n){
|
||||||
(function(t){e.exports=t.GridFieldHeader=n(119)}).call(t,function(){return this}())},function(e,t,n){(function(t){e.exports=t.GridFieldHeaderCell=n(121)}).call(t,function(){return this}())},function(e,t,n){
|
(function(t){e.exports=t.GridFieldHeader=n(120)}).call(t,function(){return this}())},function(e,t,n){(function(t){e.exports=t.GridFieldHeaderCell=n(122)}).call(t,function(){return this}())},function(e,t,n){
|
||||||
(function(t){e.exports=t.GridFieldRow=n(120)}).call(t,function(){return this}())},function(e,t,n){(function(t){e.exports=t.GridFieldTable=n(118)}).call(t,function(){return this}())},function(e,t,n){(function(t){
|
(function(t){e.exports=t.GridFieldRow=n(121)}).call(t,function(){return this}())},function(e,t,n){(function(t){e.exports=t.GridFieldTable=n(119)}).call(t,function(){return this}())},function(e,t,n){(function(t){
|
||||||
e.exports=t.HiddenField=n(132)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
e.exports=t.HiddenField=n(133)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
||||||
|
|
||||||
|
|
||||||
@ -765,7 +770,7 @@ className:this.props.className+" "+this.props.extraClass,id:this.props.id,name:t
|
|||||||
|
|
||||||
}}]),t}(d["default"])
|
}}]),t}(d["default"])
|
||||||
p.propTypes={id:u["default"].PropTypes.string,extraClass:u["default"].PropTypes.string,name:u["default"].PropTypes.string.isRequired,value:u["default"].PropTypes.any},p.defaultProps={className:"",extraClass:"",
|
p.propTypes={id:u["default"].PropTypes.string,extraClass:u["default"].PropTypes.string,name:u["default"].PropTypes.string.isRequired,value:u["default"].PropTypes.any},p.defaultProps={className:"",extraClass:"",
|
||||||
value:""},t["default"]=p},function(e,t,n){(function(t){e.exports=t.TextField=n(134)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
value:""},t["default"]=p},function(e,t,n){(function(t){e.exports=t.TextField=n(135)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
||||||
|
|
||||||
|
|
||||||
@ -774,7 +779,7 @@ e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,wri
|
|||||||
value:!0}),t.TextField=void 0
|
value:!0}),t.TextField=void 0
|
||||||
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(135),h=i(p),m=n(22),g=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(136),h=i(p),m=n(22),g=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.handleChange=n.handleChange.bind(n),n}return a(t,e),l(t,[{key:"render",value:function n(){var e=null
|
return n.handleChange=n.handleChange.bind(n),n}return a(t,e),l(t,[{key:"render",value:function n(){var e=null
|
||||||
@ -786,7 +791,7 @@ return this.props.readOnly||(s(e,{placeholder:this.props.placeholder,onChange:th
|
|||||||
id:this.props.id,value:e.target.value})}}]),t}(f["default"])
|
id:this.props.id,value:e.target.value})}}]),t}(f["default"])
|
||||||
g.propTypes={extraClass:c["default"].PropTypes.string,id:c["default"].PropTypes.string,name:c["default"].PropTypes.string.isRequired,onChange:c["default"].PropTypes.func,value:c["default"].PropTypes.oneOfType([c["default"].PropTypes.string,c["default"].PropTypes.number]),
|
g.propTypes={extraClass:c["default"].PropTypes.string,id:c["default"].PropTypes.string,name:c["default"].PropTypes.string.isRequired,onChange:c["default"].PropTypes.func,value:c["default"].PropTypes.oneOfType([c["default"].PropTypes.string,c["default"].PropTypes.number]),
|
||||||
readOnly:c["default"].PropTypes.bool,disabled:c["default"].PropTypes.bool,placeholder:c["default"].PropTypes.string,type:c["default"].PropTypes.string},g.defaultProps={value:"",extraClass:"",className:"",
|
readOnly:c["default"].PropTypes.bool,disabled:c["default"].PropTypes.bool,placeholder:c["default"].PropTypes.string,type:c["default"].PropTypes.string},g.defaultProps={value:"",extraClass:"",className:"",
|
||||||
type:"text"},t.TextField=g,t["default"]=(0,h["default"])(g)},function(e,t){e.exports=FieldHolder},function(e,t,n){(function(t){e.exports=t.Toolbar=n(137)}).call(t,function(){return this}())},function(e,t,n){
|
type:"text"},t.TextField=g,t["default"]=(0,h["default"])(g)},function(e,t){e.exports=FieldHolder},function(e,t,n){(function(t){e.exports=t.Toolbar=n(138)}).call(t,function(){return this}())},function(e,t,n){
|
||||||
"use strict"
|
"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
||||||
|
|
||||||
@ -804,7 +809,7 @@ return u["default"].createElement("div",{className:"toolbar toolbar--north"},u["
|
|||||||
|
|
||||||
}},{key:"handleBackButtonClick",value:function i(e){return"undefined"!=typeof this.props.handleBackButtonClick?void this.props.handleBackButtonClick(e):void e.preventDefault()}}]),t}(d["default"])
|
}},{key:"handleBackButtonClick",value:function i(e){return"undefined"!=typeof this.props.handleBackButtonClick?void this.props.handleBackButtonClick(e):void e.preventDefault()}}]),t}(d["default"])
|
||||||
f.propTypes={handleBackButtonClick:u["default"].PropTypes.func,showBackButton:u["default"].PropTypes.bool,breadcrumbs:u["default"].PropTypes.array},f.defaultProps={showBackButton:!1},t["default"]=f},function(e,t,n){
|
f.propTypes={handleBackButtonClick:u["default"].PropTypes.func,showBackButton:u["default"].PropTypes.bool,breadcrumbs:u["default"].PropTypes.array},f.defaultProps={showBackButton:!1},t["default"]=f},function(e,t,n){
|
||||||
(function(t){e.exports=t.Breadcrumb=n(139)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
(function(t){e.exports=t.Breadcrumb=n(140)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
||||||
|
|
||||||
|
|
||||||
@ -812,7 +817,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function s(e){return{crumbs:e.breadcrumbs
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function s(e){return{crumbs:e.breadcrumbs
|
||||||
}}Object.defineProperty(t,"__esModule",{value:!0}),t.Breadcrumb=void 0
|
}}Object.defineProperty(t,"__esModule",{value:!0}),t.Breadcrumb=void 0
|
||||||
var l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(107),h=n(140),m=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(107),h=n(141),m=function(e){
|
||||||
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),l(t,[{key:"render",value:function n(){return c["default"].createElement("ol",{className:"breadcrumb"
|
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),l(t,[{key:"render",value:function n(){return c["default"].createElement("ol",{className:"breadcrumb"
|
||||||
},this.getBreadcrumbs())}},{key:"getBreadcrumbs",value:function i(){return"undefined"==typeof this.props.crumbs?null:[].concat(this.props.crumbs.slice(0,-1).map(function(e,t){return[c["default"].createElement("li",{
|
},this.getBreadcrumbs())}},{key:"getBreadcrumbs",value:function i(){return"undefined"==typeof this.props.crumbs?null:[].concat(this.props.crumbs.slice(0,-1).map(function(e,t){return[c["default"].createElement("li",{
|
||||||
className:"breadcrumb__item"},c["default"].createElement(h.Link,{key:t,className:"breadcrumb__item-title",to:e.href,onClick:e.onClick},e.text))]}),this.props.crumbs.slice(-1).map(function(e,t){var n=["breadcrumb__icon",e.icon?e.icon.className:""].join(" ")
|
className:"breadcrumb__item"},c["default"].createElement(h.Link,{key:t,className:"breadcrumb__item-title",to:e.href,onClick:e.onClick},e.text))]}),this.props.crumbs.slice(-1).map(function(e,t){var n=["breadcrumb__icon",e.icon?e.icon.className:""].join(" ")
|
||||||
@ -820,12 +825,12 @@ className:"breadcrumb__item"},c["default"].createElement(h.Link,{key:t,className
|
|||||||
|
|
||||||
return[c["default"].createElement("li",{className:"breadcrumb__item breadcrumb__item--last"},c["default"].createElement("h2",{className:"breadcrumb__item-title breadcrumb__item-title--last",key:t},e.text,e.icon&&c["default"].createElement("span",{
|
return[c["default"].createElement("li",{className:"breadcrumb__item breadcrumb__item--last"},c["default"].createElement("h2",{className:"breadcrumb__item-title breadcrumb__item-title--last",key:t},e.text,e.icon&&c["default"].createElement("span",{
|
||||||
className:n,onClick:e.icon.action})))]}))}}]),t}(f["default"])
|
className:n,onClick:e.icon.action})))]}))}}]),t}(f["default"])
|
||||||
m.propTypes={crumbs:c["default"].PropTypes.array},t.Breadcrumb=m,t["default"]=(0,p.connect)(s)(m)},function(e,t){e.exports=ReactRouter},function(e,t,n){(function(t){e.exports=t.BreadcrumbsActions=n(142)
|
m.propTypes={crumbs:c["default"].PropTypes.array},t.Breadcrumb=m,t["default"]=(0,p.connect)(s)(m)},function(e,t){e.exports=ReactRouter},function(e,t,n){(function(t){e.exports=t.BreadcrumbsActions=n(143)
|
||||||
|
|
||||||
}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){return{type:a["default"].SET_BREADCRUMBS,payload:{breadcrumbs:e}}}Object.defineProperty(t,"__esModule",{value:!0}),t.setBreadcrumbs=r
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){return{type:a["default"].SET_BREADCRUMBS,payload:{breadcrumbs:e}}}Object.defineProperty(t,"__esModule",{value:!0}),t.setBreadcrumbs=r
|
||||||
var o=n(143),a=i(o)},function(e,t){"use strict"
|
var o=n(144),a=i(o)},function(e,t){"use strict"
|
||||||
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={SET_BREADCRUMBS:"SET_BREADCRUMBS"}},function(e,t,n){(function(t){e.exports=t.Config=n(145)}).call(t,function(){return this}())},function(e,t){
|
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={SET_BREADCRUMBS:"SET_BREADCRUMBS"}},function(e,t,n){(function(t){e.exports=t.Config=n(146)}).call(t,function(){return this}())},function(e,t){
|
||||||
"use strict"
|
"use strict"
|
||||||
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
@ -833,13 +838,13 @@ i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Obj
|
|||||||
n(this,e)}return i(e,null,[{key:"get",value:function t(e){return window.ss.config[e]}},{key:"getAll",value:function r(){return window.ss.config}},{key:"getSection",value:function o(e){return window.ss.config.sections[e]
|
n(this,e)}return i(e,null,[{key:"get",value:function t(e){return window.ss.config[e]}},{key:"getAll",value:function r(){return window.ss.config}},{key:"getSection",value:function o(e){return window.ss.config.sections[e]
|
||||||
|
|
||||||
}}]),e}()
|
}}]),e}()
|
||||||
t["default"]=r},function(e,t,n){(function(t){e.exports=t.ReducerRegister=n(147)}).call(t,function(){return this}())},function(e,t){"use strict"
|
t["default"]=r},function(e,t,n){(function(t){e.exports=t.ReducerRegister=n(148)}).call(t,function(){return this}())},function(e,t){"use strict"
|
||||||
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),r={},o=function(){function e(){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),r={},o=function(){function e(){
|
||||||
n(this,e)}return i(e,[{key:"add",value:function t(e,n){if("undefined"!=typeof r[e])throw new Error("Reducer already exists at '"+e+"'")
|
n(this,e)}return i(e,[{key:"add",value:function t(e,n){if("undefined"!=typeof r[e])throw new Error("Reducer already exists at '"+e+"'")
|
||||||
r[e]=n}},{key:"getAll",value:function o(){return r}},{key:"getByKey",value:function a(e){return r[e]}},{key:"remove",value:function s(e){delete r[e]}}]),e}()
|
r[e]=n}},{key:"getAll",value:function o(){return r}},{key:"getByKey",value:function a(e){return r[e]}},{key:"remove",value:function s(e){delete r[e]}}]),e}()
|
||||||
window.ss=window.ss||{},window.ss.reducerRegister=window.ss.reducerRegister||new o,t["default"]=window.ss.reducerRegister},function(e,t,n){(function(t){e.exports=t.ReactRouteRegister=n(149)}).call(t,function(){
|
window.ss=window.ss||{},window.ss.reducerRegister=window.ss.reducerRegister||new o,t["default"]=window.ss.reducerRegister},function(e,t,n){(function(t){e.exports=t.ReactRouteRegister=n(150)}).call(t,function(){
|
||||||
return this}())},function(e,t){"use strict"
|
return this}())},function(e,t){"use strict"
|
||||||
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
@ -858,7 +863,7 @@ return t.path===e})
|
|||||||
return i<0?null:n.splice(i,1)[0]}}]),e}()
|
return i<0?null:n.splice(i,1)[0]}}]),e}()
|
||||||
window.ss=window.ss||{},window.ss.routeRegister=window.ss.routeRegister||new o,t["default"]=window.ss.routeRegister},function(e,t,n){(function(t){e.exports=t.Injector=n(104)}).call(t,function(){return this
|
window.ss=window.ss||{},window.ss.routeRegister=window.ss.routeRegister||new o,t["default"]=window.ss.routeRegister},function(e,t,n){(function(t){e.exports=t.Injector=n(104)}).call(t,function(){return this
|
||||||
|
|
||||||
}())},function(e,t,n){(function(t){e.exports=t.Router=n(152)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
}())},function(e,t,n){(function(t){e.exports=t.Router=n(153)}).call(t,function(){return this}())},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){var t=c["default"].getAbsoluteBase(),n=f["default"].resolve(t,e)
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){var t=c["default"].getAbsoluteBase(),n=f["default"].resolve(t,e)
|
||||||
return 0!==n.indexOf(t)?n:n.substring(t.length-1)}function o(e){return function(t,n,i,r){return e(c["default"].resolveURLToBase(t),n,i,r)}}function a(e){var t=new c["default"].Route(e)
|
return 0!==n.indexOf(t)?n:n.substring(t.length-1)}function o(e){return function(t,n,i,r){return e(c["default"].resolveURLToBase(t),n,i,r)}}function a(e){var t=new c["default"].Route(e)
|
||||||
return t.match(c["default"].current,{})}function s(){return c["default"].absoluteBaseURL}function l(e){c["default"].absoluteBaseURL=e
|
return t.match(c["default"].current,{})}function s(){return c["default"].absoluteBaseURL}function l(e){c["default"].absoluteBaseURL=e
|
||||||
@ -866,7 +871,7 @@ var t=document.createElement("a")
|
|||||||
t.href=e
|
t.href=e
|
||||||
var n=t.pathname
|
var n=t.pathname
|
||||||
n=n.replace(/\/$/,""),n.match(/^[^\/]/)&&(n="/"+n),c["default"].base(n)}Object.defineProperty(t,"__esModule",{value:!0})
|
n=n.replace(/\/$/,""),n.match(/^[^\/]/)&&(n="/"+n),c["default"].base(n)}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var u=n(153),c=i(u),d=n(154),f=i(d)
|
var u=n(154),c=i(u),d=n(155),f=i(d)
|
||||||
c["default"].oldshow||(c["default"].oldshow=c["default"].show),c["default"].setAbsoluteBase=l.bind(c["default"]),c["default"].getAbsoluteBase=s.bind(c["default"]),c["default"].resolveURLToBase=r.bind(c["default"]),
|
c["default"].oldshow||(c["default"].oldshow=c["default"].show),c["default"].setAbsoluteBase=l.bind(c["default"]),c["default"].getAbsoluteBase=s.bind(c["default"]),c["default"].resolveURLToBase=r.bind(c["default"]),
|
||||||
c["default"].show=o(c["default"].oldshow),c["default"].routeAppliesToCurrentLocation=a,window.ss=window.ss||{},window.ss.router=window.ss.router||c["default"],t["default"]=window.ss.router},function(e,t){
|
c["default"].show=o(c["default"].oldshow),c["default"].routeAppliesToCurrentLocation=a,window.ss=window.ss||{},window.ss.router=window.ss.router||c["default"],t["default"]=window.ss.router},function(e,t){
|
||||||
e.exports=Page},function(e,t,n){"use strict"
|
e.exports=Page},function(e,t,n){"use strict"
|
||||||
@ -876,10 +881,10 @@ function i(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,
|
|||||||
var r=new i
|
var r=new i
|
||||||
return r.parse(e,t,n),r}function o(e){return u.isString(e)&&(e=r(e)),e instanceof i?e.format():i.prototype.format.call(e)}function a(e,t){return r(e,!1,!0).resolve(t)}function s(e,t){return e?r(e,!1,!0).resolveObject(t):t
|
return r.parse(e,t,n),r}function o(e){return u.isString(e)&&(e=r(e)),e instanceof i?e.format():i.prototype.format.call(e)}function a(e,t){return r(e,!1,!0).resolve(t)}function s(e,t){return e?r(e,!1,!0).resolveObject(t):t
|
||||||
|
|
||||||
}var l=n(155),u=n(156)
|
}var l=n(156),u=n(157)
|
||||||
t.parse=r,t.resolve=a,t.resolveObject=s,t.format=o,t.Url=i
|
t.parse=r,t.resolve=a,t.resolveObject=s,t.format=o,t.Url=i
|
||||||
var c=/^([a-z0-9.+-]+:)/i,d=/:[0-9]*$/,f=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,p=["<",">",'"',"`"," ","\r","\n","\t"],h=["{","}","|","\\","^","`"].concat(p),m=["'"].concat(h),g=["%","/","?",";","#"].concat(m),v=["/","?","#"],y=255,b=/^[+a-z0-9A-Z_-]{0,63}$/,_=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,w={
|
var c=/^([a-z0-9.+-]+:)/i,d=/:[0-9]*$/,f=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,p=["<",">",'"',"`"," ","\r","\n","\t"],h=["{","}","|","\\","^","`"].concat(p),m=["'"].concat(h),g=["%","/","?",";","#"].concat(m),v=["/","?","#"],y=255,b=/^[+a-z0-9A-Z_-]{0,63}$/,_=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,w={
|
||||||
javascript:!0,"javascript:":!0},C={javascript:!0,"javascript:":!0},T={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},E=n(157)
|
javascript:!0,"javascript:":!0},C={javascript:!0,"javascript:":!0},T={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},E=n(158)
|
||||||
i.prototype.parse=function(e,t,n){if(!u.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e)
|
i.prototype.parse=function(e,t,n){if(!u.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e)
|
||||||
var i=e.indexOf("?"),r=i!==-1&&i<e.indexOf("#")?"?":"#",o=e.split(r),a=/\\/g
|
var i=e.indexOf("?"),r=i!==-1&&i<e.indexOf("#")?"?":"#",o=e.split(r),a=/\\/g
|
||||||
o[0]=o[0].replace(a,"/"),e=o.join(r)
|
o[0]=o[0].replace(a,"/"),e=o.join(r)
|
||||||
@ -976,7 +981,7 @@ w={version:"1.3.2",ucs2:{decode:u,encode:c},decode:h,encode:m,toASCII:v,toUnicod
|
|||||||
},function(e,t){"use strict"
|
},function(e,t){"use strict"
|
||||||
e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){
|
e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){
|
||||||
"use strict"
|
"use strict"
|
||||||
t.decode=t.parse=n(158),t.encode=t.stringify=n(159)},function(e,t){"use strict"
|
t.decode=t.parse=n(159),t.encode=t.stringify=n(160)},function(e,t){"use strict"
|
||||||
function n(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,i,r){t=t||"&",i=i||"="
|
function n(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,i,r){t=t||"&",i=i||"="
|
||||||
var o={}
|
var o={}
|
||||||
if("string"!=typeof e||0===e.length)return o
|
if("string"!=typeof e||0===e.length)return o
|
||||||
@ -1022,7 +1027,7 @@ return e.replace(/^#/,"")},cleanHash:function O(e){return u.stripHash(e.replace(
|
|||||||
return!(!t.protocol||t.domain===document.domain)},hasProtocol:function k(e){return/^(:?\w+:)/.test(e)}}
|
return!(!t.protocol||t.domain===document.domain)},hasProtocol:function k(e){return/^(:?\w+:)/.test(e)}}
|
||||||
o["default"].path=u},function(e,t,n){(function(e){"use strict"
|
o["default"].path=u},function(e,t,n){(function(e){"use strict"
|
||||||
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i)
|
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i)
|
||||||
n(162),r["default"].widget("ssui.button",r["default"].ui.button,{options:{alternate:{icon:null,text:null},showingAlternate:!1},toggleAlternate:function o(){this._trigger("ontogglealternate")!==!1&&(this.options.alternate.icon||this.options.alternate.text)&&(this.options.showingAlternate=!this.options.showingAlternate,
|
n(163),r["default"].widget("ssui.button",r["default"].ui.button,{options:{alternate:{icon:null,text:null},showingAlternate:!1},toggleAlternate:function o(){this._trigger("ontogglealternate")!==!1&&(this.options.alternate.icon||this.options.alternate.text)&&(this.options.showingAlternate=!this.options.showingAlternate,
|
||||||
this.refresh())},_refreshAlternate:function a(){this._trigger("beforerefreshalternate"),(this.options.alternate.icon||this.options.alternate.text)&&(this.options.showingAlternate?(this.element.find(".ui-button-icon-primary").hide(),
|
this.refresh())},_refreshAlternate:function a(){this._trigger("beforerefreshalternate"),(this.options.alternate.icon||this.options.alternate.text)&&(this.options.showingAlternate?(this.element.find(".ui-button-icon-primary").hide(),
|
||||||
this.element.find(".ui-button-text").hide(),this.element.find(".ui-button-icon-alternate").show(),this.element.find(".ui-button-text-alternate").show()):(this.element.find(".ui-button-icon-primary").show(),
|
this.element.find(".ui-button-text").hide(),this.element.find(".ui-button-icon-alternate").show(),this.element.find(".ui-button-text-alternate").show()):(this.element.find(".ui-button-icon-primary").show(),
|
||||||
this.element.find(".ui-button-text").show(),this.element.find(".ui-button-icon-alternate").hide(),this.element.find(".ui-button-text-alternate").hide()),this._trigger("afterrefreshalternate"))},_resetButton:function s(){
|
this.element.find(".ui-button-text").show(),this.element.find(".ui-button-icon-alternate").hide(),this.element.find(".ui-button-text-alternate").hide()),this._trigger("afterrefreshalternate"))},_resetButton:function s(){
|
||||||
@ -1062,7 +1067,7 @@ o.find("*").add(o).disableSelection()},destroy:function m(){this.element.unbind(
|
|||||||
function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e
|
function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e
|
||||||
|
|
||||||
},_jQuery=__webpack_require__(1),_jQuery2=_interopRequireDefault(_jQuery)
|
},_jQuery=__webpack_require__(1),_jQuery2=_interopRequireDefault(_jQuery)
|
||||||
__webpack_require__(161)
|
__webpack_require__(162)
|
||||||
var windowWidth,windowHeight
|
var windowWidth,windowHeight
|
||||||
_jQuery2["default"].noConflict(),window.ss=window.ss||{},window.ss.debounce=function(e,t,n){var i,r,o,a=function s(){i=null,n||e.apply(r,o)}
|
_jQuery2["default"].noConflict(),window.ss=window.ss||{},window.ss.debounce=function(e,t,n){var i,r,o,a=function s(){i=null,n||e.apply(r,o)}
|
||||||
return function(){var s=n&&!i
|
return function(){var s=n&&!i
|
||||||
@ -1347,7 +1352,7 @@ l&&"#"!=l?(l=l.split("?")[0],t.jstree("deselect_all"),t.jstree("uncheck_all"),e.
|
|||||||
s.loadPanel(l)):t.removeForm()}})}}),e(".cms-content .cms-content-fields").entwine({redraw:function r(){window.debug&&console.log("redraw",this.attr("class"),this.get(0))}}),e(".cms-content .cms-content-header, .cms-content .cms-content-actions").entwine({
|
s.loadPanel(l)):t.removeForm()}})}}),e(".cms-content .cms-content-fields").entwine({redraw:function r(){window.debug&&console.log("redraw",this.attr("class"),this.get(0))}}),e(".cms-content .cms-content-header, .cms-content .cms-content-actions").entwine({
|
||||||
redraw:function o(){window.debug&&console.log("redraw",this.attr("class"),this.get(0)),this.height("auto"),this.height(this.innerHeight()-this.css("padding-top")-this.css("padding-bottom"))}})})},function(e,t,n){
|
redraw:function o(){window.debug&&console.log("redraw",this.attr("class"),this.get(0)),this.height("auto"),this.height(this.innerHeight()-this.css("padding-top")-this.css("padding-bottom"))}})})},function(e,t,n){
|
||||||
(function(e){"use strict"
|
(function(e){"use strict"
|
||||||
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i),o=n(114),a=t(o)
|
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i),o=n(115),a=t(o)
|
||||||
window.onbeforeunload=function(e){var t=(0,r["default"])(".cms-edit-form")
|
window.onbeforeunload=function(e){var t=(0,r["default"])(".cms-edit-form")
|
||||||
if(t.trigger("beforesubmitform"),t.is(".changed")&&!t.is(".discardchanges"))return a["default"]._t("LeftAndMain.CONFIRMUNSAVEDSHORT")},r["default"].entwine("ss",function(e){e(".cms-edit-form").entwine({
|
if(t.trigger("beforesubmitform"),t.is(".changed")&&!t.is(".discardchanges"))return a["default"]._t("LeftAndMain.CONFIRMUNSAVEDSHORT")},r["default"].entwine("ss",function(e){e(".cms-edit-form").entwine({
|
||||||
PlaceholderHtml:"",ChangeTrackerOptions:{ignoreFieldSelector:".no-change-track, .ss-upload :input, .cms-navigator :input"},ValidationErrorShown:!1,onadd:function t(){var e=this
|
PlaceholderHtml:"",ChangeTrackerOptions:{ignoreFieldSelector:".no-change-track, .ss-upload :input, .cms-navigator :input"},ValidationErrorShown:!1,onadd:function t(){var e=this
|
||||||
@ -1442,7 +1447,7 @@ this.toggleCSS(t),this.toggleIndicator(t),this._super()},toggleCSS:function R(e)
|
|||||||
void 0===t?e.setPersistedCollapsedState(e.hasClass("collapsed")):void 0!==t&&i===!1&&e.clearPersistedCollapsedState(),e.setPersistedStickyState(i),this.toggleCSS(i),this.toggleIndicator(i),this._super()
|
void 0===t?e.setPersistedCollapsedState(e.hasClass("collapsed")):void 0!==t&&i===!1&&e.clearPersistedCollapsedState(),e.setPersistedStickyState(i),this.toggleCSS(i),this.toggleIndicator(i),this._super()
|
||||||
|
|
||||||
}})})},function(e,t,n){"use strict"
|
}})})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(114),s=i(a)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(115),s=i(a)
|
||||||
o["default"].entwine("ss.preview",function(e){e(".cms-preview").entwine({AllowedStates:["StageLink","LiveLink","ArchiveLink"],CurrentStateName:null,CurrentSizeName:"auto",IsPreviewEnabled:!1,DefaultMode:"split",
|
o["default"].entwine("ss.preview",function(e){e(".cms-preview").entwine({AllowedStates:["StageLink","LiveLink","ArchiveLink"],CurrentStateName:null,CurrentSizeName:"auto",IsPreviewEnabled:!1,DefaultMode:"split",
|
||||||
Sizes:{auto:{width:"100%",height:"100%"},mobile:{width:"335px",height:"568px"},mobileLandscape:{width:"583px",height:"320px"},tablet:{width:"783px",height:"1024px"},tabletLandscape:{width:"1039px",height:"768px"
|
Sizes:{auto:{width:"100%",height:"100%"},mobile:{width:"335px",height:"568px"},mobileLandscape:{width:"583px",height:"320px"},tablet:{width:"783px",height:"1024px"},tabletLandscape:{width:"1039px",height:"768px"
|
||||||
},desktop:{width:"1024px",height:"800px"}},changeState:function t(n,i){var r=this,o=this._getNavigatorStates()
|
},desktop:{width:"1024px",height:"800px"}},changeState:function t(n,i){var r=this,o=this._getNavigatorStates()
|
||||||
@ -1519,7 +1524,7 @@ e(".cms-preview").changeSize(n)}}),e(".preview-selector select.preview-dropdown"
|
|||||||
return"undefined"!=typeof i&&n.removeClass(i),n.addClass(t),n.attr("data-icon",t),this}}),e(".preview-mode-selector .chosen-drop li:last-child").entwine({onmatch:function U(){e(".preview-mode-selector").hasClass("split-disabled")?this.parent().append('<div class="disabled-tooltip"></div>'):this.parent().append('<div class="disabled-tooltip" style="display: none;"></div>')
|
return"undefined"!=typeof i&&n.removeClass(i),n.addClass(t),n.attr("data-icon",t),this}}),e(".preview-mode-selector .chosen-drop li:last-child").entwine({onmatch:function U(){e(".preview-mode-selector").hasClass("split-disabled")?this.parent().append('<div class="disabled-tooltip"></div>'):this.parent().append('<div class="disabled-tooltip" style="display: none;"></div>')
|
||||||
|
|
||||||
}}),e(".preview-device-outer").entwine({onclick:function L(){this.parent(".preview__device").toggleClass("rotate")}})})},function(e,t,n){(function(e){"use strict"
|
}}),e(".preview-device-outer").entwine({onclick:function L(){this.parent(".preview__device").toggleClass("rotate")}})})},function(e,t,n){(function(e){"use strict"
|
||||||
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i),o=n(114),a=t(o)
|
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i),o=n(115),a=t(o)
|
||||||
r["default"].entwine("ss.tree",function(t){t("#Form_BatchActionsForm").entwine({Actions:[],getTree:function n(){return t(".cms-tree")},fromTree:{oncheck_node:function i(e,t){this.serializeFromTree()},onuncheck_node:function r(e,t){
|
r["default"].entwine("ss.tree",function(t){t("#Form_BatchActionsForm").entwine({Actions:[],getTree:function n(){return t(".cms-tree")},fromTree:{oncheck_node:function i(e,t){this.serializeFromTree()},onuncheck_node:function r(e,t){
|
||||||
this.serializeFromTree()}},onmatch:function o(){var e=this
|
this.serializeFromTree()}},onmatch:function o(){var e=this
|
||||||
e.getTree().bind("load_node.jstree",function(t,n){e.refreshSelected()})},onunmatch:function s(){var e=this
|
e.getTree().bind("load_node.jstree",function(t,n){e.refreshSelected()})},onunmatch:function s(){var e=this
|
||||||
@ -1587,7 +1592,7 @@ this.addClass("description-toggle-enabled"),n.on("click",function(){i[e?"hide":"
|
|||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
||||||
o["default"].entwine("ss",function(e){e(".TreeDropdownField").entwine({"from .cms-container form":{onaftersubmitform:function t(e){this.find(".tree-holder").empty(),this._super()}}})})},function(e,t,n){
|
o["default"].entwine("ss",function(e){e(".TreeDropdownField").entwine({"from .cms-container form":{onaftersubmitform:function t(e){this.find(".tree-holder").empty(),this._super()}}})})},function(e,t,n){
|
||||||
"use strict"
|
"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(5),s=i(a),l=n(176),u=i(l),c=n(107),d=n(177),f=i(d)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(5),s=i(a),l=n(177),u=i(l),c=n(107),d=n(178),f=i(d)
|
||||||
o["default"].entwine("ss",function(e){e(".cms-content-actions .add-to-campaign-action,#add-to-campaign__action").entwine({onclick:function t(){var t=e("#add-to-campaign__dialog-wrapper")
|
o["default"].entwine("ss",function(e){e(".cms-content-actions .add-to-campaign-action,#add-to-campaign__action").entwine({onclick:function t(){var t=e("#add-to-campaign__dialog-wrapper")
|
||||||
return t.length||(t=e('<div id="add-to-campaign__dialog-wrapper" />'),e("body").append(t)),t.open(),!1}}),e("#add-to-campaign__dialog-wrapper").entwine({onunmatch:function n(){this._clearModal()},open:function i(){
|
return t.length||(t=e('<div id="add-to-campaign__dialog-wrapper" />'),e("body").append(t)),t.open(),!1}}),e("#add-to-campaign__dialog-wrapper").entwine({onunmatch:function n(){this._clearModal()},open:function i(){
|
||||||
this._renderModal(!0)},close:function r(){this._renderModal(!1)},_renderModal:function o(t){var n=this,i=function h(){return n.close()},r=function m(){return n._handleSubmitModal.apply(n,arguments)},o=e("form.cms-edit-form :input[name=ID]").val(),a=window.ss.store,l="SilverStripe\\CMS\\Controllers\\CMSPageEditController",d=a.getState().config.sections[l],p=d.form.AddToCampaignForm.schemaUrl+"/"+o
|
this._renderModal(!0)},close:function r(){this._renderModal(!1)},_renderModal:function o(t){var n=this,i=function h(){return n.close()},r=function m(){return n._handleSubmitModal.apply(n,arguments)},o=e("form.cms-edit-form :input[name=ID]").val(),a=window.ss.store,l="SilverStripe\\CMS\\Controllers\\CMSPageEditController",d=a.getState().config.sections[l],p=d.form.AddToCampaignForm.schemaUrl+"/"+o
|
||||||
@ -1597,7 +1602,7 @@ u["default"].render(s["default"].createElement(c.Provider,{store:a},s["default"]
|
|||||||
responseClassGood:"modal__response modal__response--good"})),this[0])},_clearModal:function a(){u["default"].unmountComponentAtNode(this[0])},_handleSubmitModal:function l(e,t,n){return n()}})})},,function(e,t){
|
responseClassGood:"modal__response modal__response--good"})),this[0])},_clearModal:function a(){u["default"].unmountComponentAtNode(this[0])},_handleSubmitModal:function l(e,t,n){return n()}})})},,function(e,t){
|
||||||
e.exports=FormBuilderModal},function(e,t,n){"use strict"
|
e.exports=FormBuilderModal},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
||||||
n(163),n(179)
|
n(164),n(180)
|
||||||
var a=function s(e){var t=(0,o["default"])((0,o["default"])(this).contents()).find(".message")
|
var a=function s(e){var t=(0,o["default"])((0,o["default"])(this).contents()).find(".message")
|
||||||
if(t&&t.html()){var n=(0,o["default"])(window.parent.document).find("#Form_EditForm_Members").get(0)
|
if(t&&t.html()){var n=(0,o["default"])(window.parent.document).find("#Form_EditForm_Members").get(0)
|
||||||
n&&n.refresh()
|
n&&n.refresh()
|
||||||
@ -1625,7 +1630,7 @@ e(this).prop("checked","checked")}):t.each(function(){e(this).prop("checked",e(t
|
|||||||
|
|
||||||
})}})})},function(e,t,n){"use strict"
|
})}})})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
||||||
n(163),o["default"].entwine("ss",function(e){e(".cms-content-tools #Form_SearchForm").entwine({onsubmit:function t(e){this.trigger("beforeSubmit")}}),e(".importSpec").entwine({onmatch:function n(){this.find("div.details").hide(),
|
n(164),o["default"].entwine("ss",function(e){e(".cms-content-tools #Form_SearchForm").entwine({onsubmit:function t(e){this.trigger("beforeSubmit")}}),e(".importSpec").entwine({onmatch:function n(){this.find("div.details").hide(),
|
||||||
this.find("a.detailsLink").click(function(){return e("#"+e(this).attr("href").replace(/.*#/,"")).slideToggle(),!1}),this._super()},onunmatch:function i(){this._super()}})})},function(e,t,n){"use strict"
|
this.find("a.detailsLink").click(function(){return e("#"+e(this).attr("href").replace(/.*#/,"")).slideToggle(),!1}),this._super()},onunmatch:function i(){this._super()}})})},function(e,t,n){"use strict"
|
||||||
|
|
||||||
|
|
||||||
@ -1640,8 +1645,8 @@ t.toggleClass("active"),t.find(".toggle-content").css("minHeight",n)}})},functio
|
|||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r);(0,o["default"])(document).on("click",".confirmedpassword .showOnClick a",function(){var e=(0,o["default"])(".showOnClickContainer",(0,
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r);(0,o["default"])(document).on("click",".confirmedpassword .showOnClick a",function(){var e=(0,o["default"])(".showOnClickContainer",(0,
|
||||||
o["default"])(this).parent())
|
o["default"])(this).parent())
|
||||||
return e.toggle("fast",function(){e.find('input[type="hidden"]').val(e.is(":visible")?1:0)}),!1})},function(e,t,n){"use strict"
|
return e.toggle("fast",function(){e.find('input[type="hidden"]').val(e.is(":visible")?1:0)}),!1})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(114),s=i(a)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(115),s=i(a)
|
||||||
window.tmpl=n(184),n(185),n(186),o["default"].widget("blueimpUIX.fileupload",o["default"].blueimpUI.fileupload,{_initTemplates:function l(){this.options.templateContainer=document.createElement(this._files.prop("nodeName")),
|
window.tmpl=n(185),n(186),n(187),o["default"].widget("blueimpUIX.fileupload",o["default"].blueimpUI.fileupload,{_initTemplates:function l(){this.options.templateContainer=document.createElement(this._files.prop("nodeName")),
|
||||||
this.options.uploadTemplate=window.tmpl(this.options.uploadTemplateName),this.options.downloadTemplate=window.tmpl(this.options.downloadTemplateName)},_enableFileInputButton:function u(){o["default"].blueimpUI.fileupload.prototype._enableFileInputButton.call(this),
|
this.options.uploadTemplate=window.tmpl(this.options.uploadTemplateName),this.options.downloadTemplate=window.tmpl(this.options.downloadTemplateName)},_enableFileInputButton:function u(){o["default"].blueimpUI.fileupload.prototype._enableFileInputButton.call(this),
|
||||||
this.element.find(".ss-uploadfield-addfile").show()},_disableFileInputButton:function c(){o["default"].blueimpUI.fileupload.prototype._disableFileInputButton.call(this),this.element.find(".ss-uploadfield-addfile").hide()
|
this.element.find(".ss-uploadfield-addfile").show()},_disableFileInputButton:function c(){o["default"].blueimpUI.fileupload.prototype._disableFileInputButton.call(this),this.element.find(".ss-uploadfield-addfile").hide()
|
||||||
|
|
||||||
@ -1751,22 +1756,22 @@ t.length&&t.removeClass("selected")
|
|||||||
var n=e.nextAll("li.selected")
|
var n=e.nextAll("li.selected")
|
||||||
n.length&&n.removeClass("selected"),(0,o["default"])(this).focus()})})},function(e,t,n){"use strict"
|
n.length&&n.removeClass("selected"),(0,o["default"])(this).focus()})})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
||||||
n(162),o["default"].fn.extend({ssDatepicker:function a(e){return(0,o["default"])(this).each(function(){if(!((0,o["default"])(this).prop("disabled")||(0,o["default"])(this).prop("readonly")||(0,o["default"])(this).data("datepicker"))){
|
n(163),o["default"].fn.extend({ssDatepicker:function a(e){return(0,o["default"])(this).each(function(){if(!((0,o["default"])(this).prop("disabled")||(0,o["default"])(this).prop("readonly")||(0,o["default"])(this).data("datepicker"))){
|
||||||
(0,o["default"])(this).siblings("button").addClass("ui-icon ui-icon-calendar")
|
(0,o["default"])(this).siblings("button").addClass("ui-icon ui-icon-calendar")
|
||||||
var t=(0,o["default"])(this).closest(".field.date"),n=o["default"].extend(e||{},(0,o["default"])(this).data(),(0,o["default"])(this).data("jqueryuiconfig"),{})
|
var t=(0,o["default"])(this).closest(".field.date"),n=o["default"].extend(e||{},(0,o["default"])(this).data(),(0,o["default"])(this).data("jqueryuiconfig"),{})
|
||||||
n.showcalendar&&(n.locale&&o["default"].datepicker.regional[n.locale]&&(n=o["default"].extend(n,o["default"].datepicker.regional[n.locale],{})),n.min&&(n.minDate=o["default"].datepicker.parseDate("yy-mm-dd",n.min)),
|
n.showcalendar&&(n.locale&&o["default"].datepicker.regional[n.locale]&&(n=o["default"].extend(n,o["default"].datepicker.regional[n.locale],{})),n.min&&(n.minDate=o["default"].datepicker.parseDate("yy-mm-dd",n.min)),
|
||||||
n.max&&(n.maxDate=o["default"].datepicker.parseDate("yy-mm-dd",n.max)),n.dateFormat=n.jquerydateformat,(0,o["default"])(this).datepicker(n))}})}}),(0,o["default"])(document).on("click",".field.date input.text,input.text.date",function(){
|
n.max&&(n.maxDate=o["default"].datepicker.parseDate("yy-mm-dd",n.max)),n.dateFormat=n.jquerydateformat,(0,o["default"])(this).datepicker(n))}})}}),(0,o["default"])(document).on("click",".field.date input.text,input.text.date",function(){
|
||||||
(0,o["default"])(this).ssDatepicker(),(0,o["default"])(this).data("datepicker")&&(0,o["default"])(this).datepicker("show")})},function(e,t,n){"use strict"
|
(0,o["default"])(this).ssDatepicker(),(0,o["default"])(this).data("datepicker")&&(0,o["default"])(this).datepicker("show")})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
||||||
n(162),o["default"].entwine("ss",function(e){e(".ss-toggle").entwine({onadd:function t(){this._super(),this.accordion({heightStyle:"content",collapsible:!0,active:!this.hasClass("ss-toggle-start-closed")&&0
|
n(163),o["default"].entwine("ss",function(e){e(".ss-toggle").entwine({onadd:function t(){this._super(),this.accordion({heightStyle:"content",collapsible:!0,active:!this.hasClass("ss-toggle-start-closed")&&0
|
||||||
})},onremove:function n(){this.data("accordion")&&this.accordion("destroy"),this._super()},getTabSet:function i(){return this.closest(".ss-tabset")},fromTabSet:{ontabsshow:function r(){this.accordion("resize")
|
})},onremove:function n(){this.data("accordion")&&this.accordion("destroy"),this._super()},getTabSet:function i(){return this.closest(".ss-tabset")},fromTabSet:{ontabsshow:function r(){this.accordion("resize")
|
||||||
|
|
||||||
}}})})},function(e,t,n){"use strict"
|
}}})})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
||||||
o["default"].entwine("ss",function(e){e(".memberdatetimeoptionset").entwine({onmatch:function t(){this.find(".toggle-content").hide(),this._super()}}),e(".memberdatetimeoptionset .toggle").entwine({onclick:function n(t){
|
o["default"].entwine("ss",function(e){e(".memberdatetimeoptionset").entwine({onmatch:function t(){this.find(".toggle-content").hide(),this._super()}}),e(".memberdatetimeoptionset .toggle").entwine({onclick:function n(t){
|
||||||
return e(this).closest(".form__field-description").parent().find(".toggle-content").toggle(),!1}})})},function(e,t,n){(function(e){"use strict"
|
return e(this).closest(".form__field-description").parent().find(".toggle-content").toggle(),!1}})})},function(e,t,n){(function(e){"use strict"
|
||||||
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i),o=n(114),a=t(o)
|
function t(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=t(i),o=n(115),a=t(o)
|
||||||
n(192),n(193),r["default"].entwine("ss",function(t){var n,i
|
n(193),n(194),r["default"].entwine("ss",function(t){var n,i
|
||||||
t(window).bind("resize.treedropdownfield",function(){var e=function a(){t(".TreeDropdownField").closePanel()}
|
t(window).bind("resize.treedropdownfield",function(){var e=function a(){t(".TreeDropdownField").closePanel()}
|
||||||
if(t.browser.msie&&parseInt(t.browser.version,10)<9){var r=t(window).width(),o=t(window).height()
|
if(t.browser.msie&&parseInt(t.browser.version,10)<9){var r=t(window).width(),o=t(window).height()
|
||||||
r==n&&o==i||(n=r,i=o,e())}else e()})
|
r==n&&o==i||(n=r,i=o,e())}else e()})
|
||||||
@ -1834,7 +1839,7 @@ onadd:function M(){this._super(),this.bind("change.TreeDropdownField",function()
|
|||||||
|
|
||||||
},,,function(module,exports,__webpack_require__){"use strict"
|
},,,function(module,exports,__webpack_require__){"use strict"
|
||||||
function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},_jQuery=__webpack_require__(1),_jQuery2=_interopRequireDefault(_jQuery),_i18n=__webpack_require__(114),_i18n2=_interopRequireDefault(_i18n),_react=__webpack_require__(5),_react2=_interopRequireDefault(_react),_reactDom=__webpack_require__(176),_reactDom2=_interopRequireDefault(_reactDom),_reactRedux=__webpack_require__(107),ss="undefined"!=typeof window.ss?window.ss:{}
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},_jQuery=__webpack_require__(1),_jQuery2=_interopRequireDefault(_jQuery),_i18n=__webpack_require__(115),_i18n2=_interopRequireDefault(_i18n),_react=__webpack_require__(5),_react2=_interopRequireDefault(_react),_reactDom=__webpack_require__(177),_reactDom2=_interopRequireDefault(_reactDom),_reactRedux=__webpack_require__(107),ss="undefined"!=typeof window.ss?window.ss:{}
|
||||||
|
|
||||||
|
|
||||||
ss.editorWrappers={},ss.editorWrappers.tinyMCE=function(){var editorID
|
ss.editorWrappers={},ss.editorWrappers.tinyMCE=function(){var editorID
|
||||||
@ -2072,7 +2077,7 @@ return a&&a.not(c).length&&a.replaceWith(c),l&&l.prepend(s),a||(n.repaint(),n.in
|
|||||||
|
|
||||||
e.noticeAdd({text:i,type:n,stayTime:5e3,inEffect:{left:"0",opacity:"show"}})}})})},function(e,t,n){"use strict"
|
e.noticeAdd({text:i,type:n,stayTime:5e3,inEffect:{left:"0",opacity:"show"}})}})})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r)
|
||||||
n(162),n(196),n(192),o["default"].entwine("ss",function(e){e(".ss-tabset").entwine({IgnoreTabState:!1,onadd:function t(){var e=window.location.hash
|
n(163),n(197),n(193),o["default"].entwine("ss",function(e){e(".ss-tabset").entwine({IgnoreTabState:!1,onadd:function t(){var e=window.location.hash
|
||||||
this.redrawTabs(),""!==e&&this.openTabFromURL(e),this._super()},onremove:function n(){this.data("tabs")&&this.tabs("destroy"),this._super()},redrawTabs:function i(){this.rewriteHashlinks(),this.tabs()},
|
this.redrawTabs(),""!==e&&this.openTabFromURL(e),this._super()},onremove:function n(){this.data("tabs")&&this.tabs("destroy"),this._super()},redrawTabs:function i(){this.rewriteHashlinks(),this.tabs()},
|
||||||
openTabFromURL:function r(t){var n
|
openTabFromURL:function r(t){var n
|
||||||
e.each(this.find(".ui-tabs-anchor"),function(){if(this.href.indexOf(t)!==-1&&1===e(t).length)return n=e(this),!1}),void 0!==n&&e(document).ready("ajaxComplete",function(){n.click()})},rewriteHashlinks:function o(){
|
e.each(this.find(".ui-tabs-anchor"),function(){if(this.href.indexOf(t)!==-1&&1===e(t).length)return n=e(this),!1}),void 0!==n&&e(document).ready("ajaxComplete",function(){n.click()})},rewriteHashlinks:function o(){
|
||||||
@ -2080,8 +2085,8 @@ e(this).find("ul a").each(function(){if(e(this).attr("href")){var t=e(this).attr
|
|||||||
t&&e(this).attr("href",document.location.href.replace(/#.*/,"")+t[0])}})}}),e(".ui-tabs-active .ui-tabs-anchor").entwine({onmatch:function a(){this.addClass("nav-link active")},onunmatch:function s(){this.removeClass("active")
|
t&&e(this).attr("href",document.location.href.replace(/#.*/,"")+t[0])}})}}),e(".ui-tabs-active .ui-tabs-anchor").entwine({onmatch:function a(){this.addClass("nav-link active")},onunmatch:function s(){this.removeClass("active")
|
||||||
|
|
||||||
}})})},,function(e,t,n){"use strict"
|
}})})},,function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(114),s=i(a)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),o=i(r),a=n(115),s=i(a)
|
||||||
n(162),n(192),o["default"].entwine("ss",function(e){e(".grid-field").entwine({reload:function t(n,i){var r=this,o=this.closest("form"),a=this.find(":input:focus").attr("name"),l=o.find(":input").serializeArray()
|
n(163),n(193),o["default"].entwine("ss",function(e){e(".grid-field").entwine({reload:function t(n,i){var r=this,o=this.closest("form"),a=this.find(":input:focus").attr("name"),l=o.find(":input").serializeArray()
|
||||||
|
|
||||||
|
|
||||||
n||(n={}),n.data||(n.data=[]),n.data=n.data.concat(l),window.location.search&&(n.data=window.location.search.replace(/^\?/,"")+"&"+e.param(n.data)),o.addClass("loading"),e.ajax(e.extend({},{headers:{"X-Pjax":"CurrentField"
|
n||(n={}),n.data||(n.data=[]),n.data=n.data.concat(l),window.location.search&&(n.data=window.location.search.replace(/^\?/,"")+"&"+e.param(n.data)),o.addClass("loading"),e.ajax(e.extend({},{headers:{"X-Pjax":"CurrentField"
|
||||||
@ -2148,13 +2153,13 @@ var e={},t=(0,l.combineReducers)(g["default"].getAll()),n=[c["default"]],i=h["de
|
|||||||
var p=o(l.createStore),m=p(t,e)
|
var p=o(l.createStore),m=p(t,e)
|
||||||
m.dispatch(y.setConfig(h["default"].getAll())),window.ss=window.ss||{},window.ss.store=m
|
m.dispatch(y.setConfig(h["default"].getAll())),window.ss=window.ss||{},window.ss.store=m
|
||||||
var v=new s["default"](m)
|
var v=new s["default"](m)
|
||||||
v.start(window.location.pathname)}var a=n(199),s=r(a),l=n(108),u=n(221),c=r(u),d=n(109),f=n(220),p=n(145),h=r(p),m=n(222),g=r(m),v=n(223),y=i(v),b=n(225),_=r(b),w=n(227),C=r(w),T=n(228),E=r(T),P=n(229),O=r(P),S=n(231),k=r(S),j=n(232),x=r(j),R=n(248),I=r(R),A=n(10),D=r(A)
|
v.start(window.location.pathname)}var a=n(200),s=r(a),l=n(108),u=n(222),c=r(u),d=n(110),f=n(221),p=n(146),h=r(p),m=n(223),g=r(m),v=n(224),y=i(v),b=n(226),_=r(b),w=n(227),C=r(w),T=n(228),E=r(T),P=n(229),O=r(P),S=n(231),k=r(S),j=n(232),x=r(j),R=n(248),I=r(R),A=n(10),D=r(A)
|
||||||
|
|
||||||
|
|
||||||
D["default"].polyfill(),window.onload=o},function(e,t,n){"use strict"
|
D["default"].polyfill(),window.onload=o},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=n(1),s=i(a),l=n(5),u=i(l),c=n(176),d=i(c),f=n(107),p=n(140),h=n(200),m=i(h),g=n(145),v=i(g),y=n(217),b=i(y),_=n(218),w=i(_),C=n(219),T=i(C),E=n(220),P=function(){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=n(1),s=i(a),l=n(5),u=i(l),c=n(177),d=i(c),f=n(107),p=n(141),h=n(201),m=i(h),g=n(146),v=i(g),y=n(218),b=i(y),_=n(219),w=i(_),C=n(220),T=i(C),E=n(221),P=function(){
|
||||||
function e(t){r(this,e),this.store=t
|
function e(t){r(this,e),this.store=t
|
||||||
var n=v["default"].get("absoluteBaseUrl")
|
var n=v["default"].get("absoluteBaseUrl")
|
||||||
b["default"].setAbsoluteBase(n)}return o(e,[{key:"start",value:function t(e){this.matchesLegacyRoute(e)?this.initLegacyRouter():this.initReactRouter()}},{key:"matchesLegacyRoute",value:function n(e){var t=v["default"].get("sections"),n=b["default"].resolveURLToBase(e).replace(/\/$/,"")
|
b["default"].setAbsoluteBase(n)}return o(e,[{key:"start",value:function t(e){this.matchesLegacyRoute(e)?this.initLegacyRouter():this.initReactRouter()}},{key:"matchesLegacyRoute",value:function n(e){var t=v["default"].get("sections"),n=b["default"].resolveURLToBase(e).replace(/\/$/,"")
|
||||||
@ -2186,25 +2191,25 @@ function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).app
|
|||||||
return e}}]),t}(d["default"])
|
return e}}]),t}(d["default"])
|
||||||
t["default"]=f},function(e,t){e.exports=ReactRouterRedux},function(e,t){e.exports=ReduxThunk},function(e,t){e.exports=ReducerRegister},function(e,t,n){"use strict"
|
t["default"]=f},function(e,t){e.exports=ReactRouterRedux},function(e,t){e.exports=ReduxThunk},function(e,t){e.exports=ReducerRegister},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){return{type:a["default"].SET_CONFIG,payload:{config:e}}}Object.defineProperty(t,"__esModule",{value:!0}),t.setConfig=r
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){return{type:a["default"].SET_CONFIG,payload:{config:e}}}Object.defineProperty(t,"__esModule",{value:!0}),t.setConfig=r
|
||||||
var o=n(224),a=i(o)},function(e,t){"use strict"
|
var o=n(225),a=i(o)},function(e,t){"use strict"
|
||||||
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={SET_CONFIG:"SET_CONFIG"}},function(e,t,n){"use strict"
|
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={SET_CONFIG:"SET_CONFIG"}},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=arguments[1]
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=arguments[1]
|
||||||
switch(t.type){case u["default"].SET_CONFIG:return(0,s["default"])(o({},e,t.payload.config))
|
switch(t.type){case u["default"].SET_CONFIG:return(0,s["default"])(o({},e,t.payload.config))
|
||||||
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},a=n(226),s=i(a),l=n(224),u=i(l)
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},a=n(109),s=i(a),l=n(225),u=i(l)
|
||||||
t["default"]=r},function(e,t){e.exports=DeepFreezeStrict},function(e,t,n){"use strict"
|
t["default"]=r},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(){var e=arguments.length<=0||void 0===arguments[0]?d:arguments[0],t=arguments.length<=1||void 0===arguments[1]?null:arguments[1]
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(){var e=arguments.length<=0||void 0===arguments[0]?d:arguments[0],t=arguments.length<=1||void 0===arguments[1]?null:arguments[1]
|
||||||
|
|
||||||
|
|
||||||
switch(t.type){case c["default"].SET_SCHEMA:return(0,l["default"])(a({},e,r({},t.payload.id,a({},e[t.payload.id],{id:t.payload.id,schema:t.payload.schema,state:t.payload.state}))))
|
switch(t.type){case c["default"].SET_SCHEMA:return(0,l["default"])(a({},e,r({},t.payload.id,a({},e[t.payload.id],t.payload))))
|
||||||
case c["default"].SET_SCHEMA_STATE_OVERRIDES:return(0,l["default"])(a({},e,r({},t.payload.id,a({},e[t.payload.id],{stateOverride:t.payload.stateOverride}))))
|
case c["default"].SET_SCHEMA_STATE_OVERRIDES:return(0,l["default"])(a({},e,r({},t.payload.id,a({},e[t.payload.id],{stateOverride:t.payload.stateOverride}))))
|
||||||
case c["default"].SET_SCHEMA_LOADING:return(0,l["default"])(a({},e,r({},t.payload.id,a({},e[t.payload.id],{metadata:a({},e[t.payload.id]&&e[t.payload.id].metadata,{loading:t.payload.loading})}))))
|
case c["default"].SET_SCHEMA_LOADING:return(0,l["default"])(a({},e,r({},t.payload.id,a({},e[t.payload.id],{metadata:a({},e[t.payload.id]&&e[t.payload.id].metadata,{loading:t.payload.loading})}))))
|
||||||
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var a=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var a=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e}
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e}
|
||||||
t["default"]=o
|
t["default"]=o
|
||||||
var s=n(226),l=i(s),u=n(33),c=i(u),d=(0,l["default"])({})},function(e,t,n){"use strict"
|
var s=n(109),l=i(s),u=n(33),c=i(u),d=(0,l["default"])({})},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(){var e=arguments.length<=0||void 0===arguments[0]?d:arguments[0],t=arguments[1],n=null,i=null,o=null
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(){var e=arguments.length<=0||void 0===arguments[0]?d:arguments[0],t=arguments[1],n=null,i=null,o=null
|
||||||
|
|
||||||
|
|
||||||
@ -2226,7 +2231,7 @@ case c["default"].DELETE_RECORD_SUCCESS:return i=t.payload.recordType,n=e[i],n=O
|
|||||||
|
|
||||||
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var a=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var a=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},s=n(226),l=i(s),u=n(125),c=i(u),d={}
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},s=n(109),l=i(s),u=n(126),c=i(u),d={}
|
||||||
t["default"]=o},function(e,t,n){"use strict"
|
t["default"]=o},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(){var e=arguments.length<=0||void 0===arguments[0]?c:arguments[0],t=arguments[1]
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(){var e=arguments.length<=0||void 0===arguments[0]?c:arguments[0],t=arguments[1]
|
||||||
switch(t.type){case u["default"].SET_CAMPAIGN_SELECTED_CHANGESETITEM:return(0,s["default"])(o({},e,{changeSetItemId:t.payload.changeSetItemId}))
|
switch(t.type){case u["default"].SET_CAMPAIGN_SELECTED_CHANGESETITEM:return(0,s["default"])(o({},e,{changeSetItemId:t.payload.changeSetItemId}))
|
||||||
@ -2235,7 +2240,7 @@ case u["default"].PUBLISH_CAMPAIGN_REQUEST:return(0,s["default"])(o({},e,{isPubl
|
|||||||
case u["default"].PUBLISH_CAMPAIGN_SUCCESS:case u["default"].PUBLISH_CAMPAIGN_FAILURE:return(0,s["default"])(o({},e,{isPublishing:!1}))
|
case u["default"].PUBLISH_CAMPAIGN_SUCCESS:case u["default"].PUBLISH_CAMPAIGN_FAILURE:return(0,s["default"])(o({},e,{isPublishing:!1}))
|
||||||
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},a=n(226),s=i(a),l=n(230),u=i(l),c=(0,s["default"])({campaignId:null,changeSetItemId:null,isPublishing:!1,view:null})
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},a=n(109),s=i(a),l=n(230),u=i(l),c=(0,s["default"])({campaignId:null,changeSetItemId:null,isPublishing:!1,view:null})
|
||||||
t["default"]=r},function(e,t){"use strict"
|
t["default"]=r},function(e,t){"use strict"
|
||||||
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={SET_CAMPAIGN_ACTIVE_CHANGESET:"SET_CAMPAIGN_ACTIVE_CHANGESET",SET_CAMPAIGN_SELECTED_CHANGESETITEM:"SET_CAMPAIGN_SELECTED_CHANGESETITEM",PUBLISH_CAMPAIGN_REQUEST:"PUBLISH_CAMPAIGN_REQUEST",
|
Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={SET_CAMPAIGN_ACTIVE_CHANGESET:"SET_CAMPAIGN_ACTIVE_CHANGESET",SET_CAMPAIGN_SELECTED_CHANGESETITEM:"SET_CAMPAIGN_SELECTED_CHANGESETITEM",PUBLISH_CAMPAIGN_REQUEST:"PUBLISH_CAMPAIGN_REQUEST",
|
||||||
PUBLISH_CAMPAIGN_SUCCESS:"PUBLISH_CAMPAIGN_SUCCESS",PUBLISH_CAMPAIGN_FAILURE:"PUBLISH_CAMPAIGN_FAILURE"}},function(e,t,n){"use strict"
|
PUBLISH_CAMPAIGN_SUCCESS:"PUBLISH_CAMPAIGN_SUCCESS",PUBLISH_CAMPAIGN_FAILURE:"PUBLISH_CAMPAIGN_FAILURE"}},function(e,t,n){"use strict"
|
||||||
@ -2243,11 +2248,11 @@ function i(e){return e&&e.__esModule?e:{"default":e}}function r(){var e=argument
|
|||||||
switch(t.type){case u["default"].SET_BREADCRUMBS:return(0,s["default"])(o([],t.payload.breadcrumbs))
|
switch(t.type){case u["default"].SET_BREADCRUMBS:return(0,s["default"])(o([],t.payload.breadcrumbs))
|
||||||
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
default:return e}}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},a=n(226),s=i(a),l=n(143),u=i(l),c=(0,s["default"])([])
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},a=n(109),s=i(a),l=n(144),u=i(l),c=(0,s["default"])([])
|
||||||
t["default"]=r},function(e,t,n){"use strict"
|
t["default"]=r},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=n(104),s=i(a),l=n(134),u=i(l),c=n(132),d=i(c),f=n(233),p=i(f),h=n(235),m=i(h),g=n(236),v=i(g),y=n(237),b=i(y),_=n(238),w=i(_),C=n(239),T=i(C),E=n(240),P=i(E),O=n(241),S=i(O),k=n(242),j=i(k),x=n(243),R=i(x),I=n(244),A=i(I),D=n(245),F=i(D),M=n(246),N=i(M),U=n(247),L=i(U),H=function(){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=n(104),s=i(a),l=n(135),u=i(l),c=n(133),d=i(c),f=n(233),p=i(f),h=n(235),m=i(h),g=n(236),v=i(g),y=n(237),b=i(y),_=n(238),w=i(_),C=n(239),T=i(C),E=n(240),P=i(E),O=n(241),S=i(O),k=n(242),j=i(k),x=n(243),R=i(x),I=n(244),A=i(I),D=n(245),F=i(D),M=n(246),N=i(M),U=n(247),L=i(U),H=function(){
|
||||||
function e(){r(this,e)}return o(e,[{key:"start",value:function t(){s["default"].register("TextField",u["default"]),s["default"].register("HiddenField",d["default"]),s["default"].register("CheckboxField",p["default"]),
|
function e(){r(this,e)}return o(e,[{key:"start",value:function t(){s["default"].register("TextField",u["default"]),s["default"].register("HiddenField",d["default"]),s["default"].register("CheckboxField",p["default"]),
|
||||||
s["default"].register("CheckboxSetField",m["default"]),s["default"].register("OptionsetField",v["default"]),s["default"].register("GridField",b["default"]),s["default"].register("SingleSelectField",w["default"]),
|
s["default"].register("CheckboxSetField",m["default"]),s["default"].register("OptionsetField",v["default"]),s["default"].register("GridField",b["default"]),s["default"].register("SingleSelectField",w["default"]),
|
||||||
s["default"].register("PopoverField",T["default"]),s["default"].register("HeaderField",P["default"]),s["default"].register("LiteralField",S["default"]),s["default"].register("HtmlReadonlyField",j["default"]),
|
s["default"].register("PopoverField",T["default"]),s["default"].register("HeaderField",P["default"]),s["default"].register("LiteralField",S["default"]),s["default"].register("HtmlReadonlyField",j["default"]),
|
||||||
@ -2263,7 +2268,7 @@ e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,wri
|
|||||||
value:!0})
|
value:!0})
|
||||||
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(234),f=i(d),p=n(135),h=i(p),m=n(21),g=i(m),v=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(234),f=i(d),p=n(136),h=i(p),m=n(21),g=i(m),v=function(e){
|
||||||
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),l(t,[{key:"render",value:function n(){var e=(0,h["default"])(f["default"])
|
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),l(t,[{key:"render",value:function n(){var e=(0,h["default"])(f["default"])
|
||||||
return c["default"].createElement(e,s({},this.props,{type:"checkbox",hideLabels:!0}))}}]),t}(g["default"])
|
return c["default"].createElement(e,s({},this.props,{type:"checkbox",hideLabels:!0}))}}]),t}(g["default"])
|
||||||
t["default"]=v},function(e,t,n){"use strict"
|
t["default"]=v},function(e,t,n){"use strict"
|
||||||
@ -2297,7 +2302,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0}),t.CheckboxSetField=void 0
|
value:!0}),t.CheckboxSetField=void 0
|
||||||
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(234),p=i(f),h=n(135),m=i(h),g=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(234),p=i(f),h=n(136),m=i(h),g=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.getItemKey=n.getItemKey.bind(n),n.getOptionProps=n.getOptionProps.bind(n),n.handleChange=n.handleChange.bind(n),n.getValues=n.getValues.bind(n),n}return a(t,e),s(t,[{key:"getItemKey",value:function n(e,t){
|
return n.getItemKey=n.getItemKey.bind(n),n.getOptionProps=n.getOptionProps.bind(n),n.handleChange=n.handleChange.bind(n),n.getValues=n.getValues.bind(n),n}return a(t,e),s(t,[{key:"getItemKey",value:function n(e,t){
|
||||||
@ -2321,7 +2326,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0}),t.OptionsetField=void 0
|
value:!0}),t.OptionsetField=void 0
|
||||||
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(234),p=i(f),h=n(135),m=i(h),g=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(234),p=i(f),h=n(136),m=i(h),g=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.getItemKey=n.getItemKey.bind(n),n.getOptionProps=n.getOptionProps.bind(n),n.handleChange=n.handleChange.bind(n),n}return a(t,e),s(t,[{key:"getItemKey",value:function n(e,t){return this.props.id+"-"+(e.value||"empty"+t)
|
return n.getItemKey=n.getItemKey.bind(n),n.getOptionProps=n.getOptionProps.bind(n),n.handleChange=n.handleChange.bind(n),n}return a(t,e),s(t,[{key:"getItemKey",value:function n(e,t){return this.props.id+"-"+(e.value||"empty"+t)
|
||||||
@ -2344,7 +2349,7 @@ e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,wri
|
|||||||
value:!0}),t.SingleSelectField=void 0
|
value:!0}),t.SingleSelectField=void 0
|
||||||
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(135),h=i(p),m=n(114),g=i(m),v=n(22),y=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(136),h=i(p),m=n(115),g=i(m),v=n(22),y=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.handleChange=n.handleChange.bind(n),n}return a(t,e),l(t,[{key:"render",value:function n(){var e=null
|
return n.handleChange=n.handleChange.bind(n),n}return a(t,e),l(t,[{key:"render",value:function n(){var e=null
|
||||||
@ -2420,7 +2425,7 @@ e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,wri
|
|||||||
value:!0}),t.HtmlReadonlyField=void 0
|
value:!0}),t.HtmlReadonlyField=void 0
|
||||||
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var s=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},l=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(135),h=i(p),m=n(22),g=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),u=n(5),c=i(u),d=n(21),f=i(d),p=n(136),h=i(p),m=n(22),g=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.getContent=n.getContent.bind(n),n}return a(t,e),l(t,[{key:"getContent",value:function n(){return{__html:this.props.value}}},{key:"getInputProps",value:function i(){return{bsClass:this.props.bsClass,
|
return n.getContent=n.getContent.bind(n),n}return a(t,e),l(t,[{key:"getContent",value:function n(){return{__html:this.props.value}}},{key:"getInputProps",value:function i(){return{bsClass:this.props.bsClass,
|
||||||
@ -2435,7 +2440,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0}),t.LookupField=void 0
|
value:!0}),t.LookupField=void 0
|
||||||
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(22),p=n(135),h=i(p),m=n(114),g=i(m),v=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(22),p=n(136),h=i(p),m=n(115),g=i(m),v=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.getValueCSV=n.getValueCSV.bind(n),n}return a(t,e),s(t,[{key:"getValueCSV",value:function n(){var e=this,t=this.props.value
|
return n.getValueCSV=n.getValueCSV.bind(n),n}return a(t,e),s(t,[{key:"getValueCSV",value:function n(){var e=this,t=this.props.value
|
||||||
@ -2500,7 +2505,7 @@ return u["default"].createElement(f.Tab.Pane,e,this.props.children)}}]),t}(d["de
|
|||||||
p.propTypes={name:u["default"].PropTypes.string.isRequired,extraClass:u["default"].PropTypes.string,tabClassName:u["default"].PropTypes.string},p.defaultProps={className:"",extraClass:""},t["default"]=p
|
p.propTypes={name:u["default"].PropTypes.string.isRequired,extraClass:u["default"].PropTypes.string,tabClassName:u["default"].PropTypes.string},p.defaultProps={className:"",extraClass:""},t["default"]=p
|
||||||
|
|
||||||
},function(e,t){e.exports=FormAction},function(e,t,n){"use strict"
|
},function(e,t){e.exports=FormAction},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(140),o=n(145),a=i(o),s=n(218),l=i(s),u=n(249),c=i(u)
|
function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(141),o=n(146),a=i(o),s=n(219),l=i(s),u=n(249),c=i(u)
|
||||||
document.addEventListener("DOMContentLoaded",function(){var e=a["default"].getSection("SilverStripe\\Admin\\CampaignAdmin")
|
document.addEventListener("DOMContentLoaded",function(){var e=a["default"].getSection("SilverStripe\\Admin\\CampaignAdmin")
|
||||||
l["default"].add({path:e.url,component:(0,r.withRouter)(c["default"]),childRoutes:[{path:":type/:id/:view",component:c["default"]},{path:"set/:id/:view",component:c["default"]}]})})},function(e,t,n){"use strict"
|
l["default"].add({path:e.url,component:(0,r.withRouter)(c["default"]),childRoutes:[{path:":type/:id/:view",component:c["default"]},{path:"set/:id/:view",component:c["default"]}]})})},function(e,t,n){"use strict"
|
||||||
|
|
||||||
@ -2517,7 +2522,7 @@ campaignId:e.campaign.campaignId,view:e.campaign.view,breadcrumbs:e.breadcrumbs,
|
|||||||
breadcrumbsActions:(0,m.bindActionCreators)(_,e)}}Object.defineProperty(t,"__esModule",{value:!0})
|
breadcrumbsActions:(0,m.bindActionCreators)(_,e)}}Object.defineProperty(t,"__esModule",{value:!0})
|
||||||
var c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
var c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
|
||||||
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},d=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},d=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),f=n(5),p=r(f),h=n(107),m=n(108),g=n(140),v=n(103),y=r(v),b=n(250),_=i(b),w=n(251),C=r(w),T=n(21),E=r(T),P=n(247),O=r(P),S=n(114),k=r(S),j=n(252),x=r(j),R=n(115),I=r(R),A=n(253),D=r(A),F=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),f=n(5),p=r(f),h=n(107),m=n(108),g=n(141),v=n(103),y=r(v),b=n(250),_=i(b),w=n(251),C=r(w),T=n(21),E=r(T),P=n(247),O=r(P),S=n(115),k=r(S),j=n(252),x=r(j),R=n(116),I=r(R),A=n(253),D=r(A),F=function(e){
|
||||||
function t(e){o(this,t)
|
function t(e){o(this,t)
|
||||||
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.publishApi=y["default"].createEndpointFetcher({url:n.props.sectionConfig.publishEndpoint.url,method:n.props.sectionConfig.publishEndpoint.method,defaultData:{SecurityID:n.props.securityId},payloadSchema:{
|
return n.publishApi=y["default"].createEndpointFetcher({url:n.props.sectionConfig.publishEndpoint.url,method:n.props.sectionConfig.publishEndpoint.method,defaultData:{SecurityID:n.props.securityId},payloadSchema:{
|
||||||
@ -2591,7 +2596,7 @@ var i=Object.getOwnPropertyDescriptor(e,t)
|
|||||||
if(void 0===i){var r=Object.getPrototypeOf(e)
|
if(void 0===i){var r=Object.getPrototypeOf(e)
|
||||||
return null===r?void 0:q(r,t,n)}if("value"in i)return i.value
|
return null===r?void 0:q(r,t,n)}if("value"in i)return i.value
|
||||||
var o=i.get
|
var o=i.get
|
||||||
if(void 0!==o)return o.call(n)},p=n(5),h=r(p),m=n(108),g=n(107),v=n(250),y=i(v),b=n(124),_=i(b),w=n(254),C=i(w),T=n(21),E=r(T),P=n(255),O=r(P),S=n(256),k=r(S),j=n(258),x=r(j),R=n(252),I=r(R),A=n(247),D=r(A),F=n(259),M=r(F),N=n(251),U=r(N),L=n(260),H=r(L),B=n(114),$=r(B),V=function(e){
|
if(void 0!==o)return o.call(n)},p=n(5),h=r(p),m=n(108),g=n(107),v=n(250),y=i(v),b=n(125),_=i(b),w=n(254),C=i(w),T=n(21),E=r(T),P=n(255),O=r(P),S=n(256),k=r(S),j=n(258),x=r(j),R=n(252),I=r(R),A=n(247),D=r(A),F=n(259),M=r(F),N=n(251),U=r(N),L=n(260),H=r(L),B=n(115),$=r(B),V=function(e){
|
||||||
function t(e){o(this,t)
|
function t(e){o(this,t)
|
||||||
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.handlePublish=n.handlePublish.bind(n),n.handleItemSelected=n.handleItemSelected.bind(n),n.setBreadcrumbs=n.setBreadcrumbs.bind(n),n.handleCloseItem=n.handleCloseItem.bind(n),n}return s(t,e),d(t,[{
|
return n.handlePublish=n.handlePublish.bind(n),n.handleItemSelected=n.handleItemSelected.bind(n),n.setBreadcrumbs=n.setBreadcrumbs.bind(n),n.handleCloseItem=n.handleCloseItem.bind(n),n}return s(t,e),d(t,[{
|
||||||
@ -2634,7 +2639,7 @@ function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){return{type:l
|
|||||||
payload:{campaignId:e,view:t}})}}function a(e,t,n){return function(i){i({type:l["default"].PUBLISH_CAMPAIGN_REQUEST,payload:{campaignId:n}}),e({id:n}).then(function(e){i({type:l["default"].PUBLISH_CAMPAIGN_SUCCESS,
|
payload:{campaignId:e,view:t}})}}function a(e,t,n){return function(i){i({type:l["default"].PUBLISH_CAMPAIGN_REQUEST,payload:{campaignId:n}}),e({id:n}).then(function(e){i({type:l["default"].PUBLISH_CAMPAIGN_SUCCESS,
|
||||||
payload:{campaignId:n}}),i({type:c["default"].FETCH_RECORD_SUCCESS,payload:{recordType:t,data:e}})})["catch"](function(e){i({type:l["default"].PUBLISH_CAMPAIGN_FAILURE,payload:{error:e}})})}}Object.defineProperty(t,"__esModule",{
|
payload:{campaignId:n}}),i({type:c["default"].FETCH_RECORD_SUCCESS,payload:{recordType:t,data:e}})})["catch"](function(e){i({type:l["default"].PUBLISH_CAMPAIGN_FAILURE,payload:{error:e}})})}}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0}),t.selectChangeSetItem=r,t.showCampaignView=o,t.publishCampaign=a
|
value:!0}),t.selectChangeSetItem=r,t.showCampaignView=o,t.publishCampaign=a
|
||||||
var s=n(230),l=i(s),u=n(125),c=i(u)},function(e,t,n){"use strict"
|
var s=n(230),l=i(s),u=n(126),c=i(u)},function(e,t,n){"use strict"
|
||||||
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
||||||
|
|
||||||
|
|
||||||
@ -2684,7 +2689,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0})
|
value:!0})
|
||||||
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(114),p=i(f),h=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(21),d=i(c),f=n(115),p=i(f),h=function(e){
|
||||||
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),s(t,[{key:"render",value:function n(){var e=null,t={},n=this.props.item,i=this.props.campaign
|
function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return a(t,e),s(t,[{key:"render",value:function n(){var e=null,t={},n=this.props.item,i=this.props.campaign
|
||||||
|
|
||||||
|
|
||||||
@ -2707,7 +2712,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("funct
|
|||||||
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{
|
||||||
value:!0})
|
value:!0})
|
||||||
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
|
||||||
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(114),d=i(c),f=n(21),p=i(f),h=function(e){
|
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=n(5),u=i(l),c=n(115),d=i(c),f=n(21),p=i(f),h=function(e){
|
||||||
function t(e){r(this,t)
|
function t(e){r(this,t)
|
||||||
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))
|
||||||
return n.handleBackClick=n.handleBackClick.bind(n),n}return a(t,e),s(t,[{key:"handleBackClick",value:function n(e){"function"==typeof this.props.onBack&&(e.preventDefault(),this.props.onBack(e))}},{key:"render",
|
return n.handleBackClick=n.handleBackClick.bind(n),n}return a(t,e),s(t,[{key:"handleBackClick",value:function n(e){"function"==typeof this.props.onBack&&(e.preventDefault(),this.props.onBack(e))}},{key:"render",
|
||||||
|
62
admin/client/dist/js/vendor.js
vendored
62
admin/client/dist/js/vendor.js
vendored
@ -12,8 +12,8 @@ else{i[t]=[n]
|
|||||||
var r=document.getElementsByTagName("head")[0],o=document.createElement("script")
|
var r=document.getElementsByTagName("head")[0],o=document.createElement("script")
|
||||||
o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=e.p+""+t+".js/"+({0:"CMSSecurity",1:"LeftAndMain.Ping",2:"MemberImportForm",3:"TinyMCE_SSPlugin",4:"UploadField_select",5:"bundle",6:"leaktools"
|
o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=e.p+""+t+".js/"+({0:"CMSSecurity",1:"LeftAndMain.Ping",2:"MemberImportForm",3:"TinyMCE_SSPlugin",4:"UploadField_select",5:"bundle",6:"leaktools"
|
||||||
}[t]||t)+".js",r.appendChild(o)}},e.m=t,e.c=r,e.p="",e(0)}([function(t,e,n){"use strict"
|
}[t]||t)+".js",r.appendChild(o)}},e.m=t,e.c=r,e.p="",e(0)}([function(t,e,n){"use strict"
|
||||||
n(261),n(557),n(558),n(561),n(563),n(565),n(595),n(597),n(730),n(740),n(755),n(930),n(932),n(980),n(986),n(1240),n(1248),n(1251),n(1254),n(1257),n(1258),n(162),n(192),n(196),n(1259),n(1260),n(1261),n(1262),
|
n(261),n(557),n(558),n(561),n(563),n(565),n(595),n(597),n(730),n(740),n(755),n(930),n(932),n(980),n(986),n(1240),n(1248),n(1251),n(1254),n(1257),n(1258),n(163),n(193),n(197),n(1259),n(1260),n(1261),n(1262),
|
||||||
n(193),n(1263),n(1264),n(1265),n(1266),n(1267),n(1268),n(1269),n(1270)},function(t,e){t.exports=jQuery},,,,function(t,e){t.exports=React},,,,,,function(t,e){function n(){throw new Error("setTimeout has not been defined")
|
n(194),n(1263),n(1264),n(1265),n(1266),n(1267),n(1268),n(1269),n(1270)},function(t,e){t.exports=jQuery},,,,function(t,e){t.exports=React},,,,,,function(t,e){function n(){throw new Error("setTimeout has not been defined")
|
||||||
|
|
||||||
}function r(){throw new Error("clearTimeout has not been defined")}function i(t){if(d===setTimeout)return setTimeout(t,0)
|
}function r(){throw new Error("clearTimeout has not been defined")}function i(t){if(d===setTimeout)return setTimeout(t,0)
|
||||||
if((d===n||!d)&&setTimeout)return d=setTimeout,setTimeout(t,0)
|
if((d===n||!d)&&setTimeout)return d=setTimeout,setTimeout(t,0)
|
||||||
@ -402,7 +402,7 @@ var o=n(48),a=r(o),s=n(49),l=r(s),u={all_lowercase:!0,gmail_lowercase:!0,gmail_r
|
|||||||
yahoo_lowercase:!0,yahoo_remove_subaddress:!0,icloud_lowercase:!0,icloud_remove_subaddress:!0},c=["icloud.com","me.com"],d=["hotmail.at","hotmail.be","hotmail.ca","hotmail.cl","hotmail.co.il","hotmail.co.nz","hotmail.co.th","hotmail.co.uk","hotmail.com","hotmail.com.ar","hotmail.com.au","hotmail.com.br","hotmail.com.gr","hotmail.com.mx","hotmail.com.pe","hotmail.com.tr","hotmail.com.vn","hotmail.cz","hotmail.de","hotmail.dk","hotmail.es","hotmail.fr","hotmail.hu","hotmail.id","hotmail.ie","hotmail.in","hotmail.it","hotmail.jp","hotmail.kr","hotmail.lv","hotmail.my","hotmail.ph","hotmail.pt","hotmail.sa","hotmail.sg","hotmail.sk","live.be","live.co.uk","live.com","live.com.ar","live.com.mx","live.de","live.es","live.eu","live.fr","live.it","live.nl","msn.com","outlook.at","outlook.be","outlook.cl","outlook.co.il","outlook.co.nz","outlook.co.th","outlook.com","outlook.com.ar","outlook.com.au","outlook.com.br","outlook.com.gr","outlook.com.pe","outlook.com.tr","outlook.com.vn","outlook.cz","outlook.de","outlook.dk","outlook.es","outlook.fr","outlook.hu","outlook.id","outlook.ie","outlook.in","outlook.it","outlook.jp","outlook.kr","outlook.lv","outlook.my","outlook.ph","outlook.pt","outlook.sa","outlook.sg","outlook.sk","passport.com"],f=["rocketmail.com","yahoo.ca","yahoo.co.uk","yahoo.com","yahoo.de","yahoo.fr","yahoo.in","yahoo.it","ymail.com"]
|
yahoo_lowercase:!0,yahoo_remove_subaddress:!0,icloud_lowercase:!0,icloud_remove_subaddress:!0},c=["icloud.com","me.com"],d=["hotmail.at","hotmail.be","hotmail.ca","hotmail.cl","hotmail.co.il","hotmail.co.nz","hotmail.co.th","hotmail.co.uk","hotmail.com","hotmail.com.ar","hotmail.com.au","hotmail.com.br","hotmail.com.gr","hotmail.com.mx","hotmail.com.pe","hotmail.com.tr","hotmail.com.vn","hotmail.cz","hotmail.de","hotmail.dk","hotmail.es","hotmail.fr","hotmail.hu","hotmail.id","hotmail.ie","hotmail.in","hotmail.it","hotmail.jp","hotmail.kr","hotmail.lv","hotmail.my","hotmail.ph","hotmail.pt","hotmail.sa","hotmail.sg","hotmail.sk","live.be","live.co.uk","live.com","live.com.ar","live.com.mx","live.de","live.es","live.eu","live.fr","live.it","live.nl","msn.com","outlook.at","outlook.be","outlook.cl","outlook.co.il","outlook.co.nz","outlook.co.th","outlook.com","outlook.com.ar","outlook.com.au","outlook.com.br","outlook.com.gr","outlook.com.pe","outlook.com.tr","outlook.com.vn","outlook.cz","outlook.de","outlook.dk","outlook.es","outlook.fr","outlook.hu","outlook.id","outlook.ie","outlook.in","outlook.it","outlook.jp","outlook.kr","outlook.lv","outlook.my","outlook.ph","outlook.pt","outlook.sa","outlook.sg","outlook.sk","passport.com"],f=["rocketmail.com","yahoo.ca","yahoo.co.uk","yahoo.com","yahoo.de","yahoo.fr","yahoo.in","yahoo.it","ymail.com"]
|
||||||
|
|
||||||
|
|
||||||
t.exports=e["default"]},,,,,function(t,e){t.exports=ReactRedux},function(t,e){t.exports=Redux},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(module,exports,__webpack_require__){(function(jQuery){
|
t.exports=e["default"]},,,,,function(t,e){t.exports=ReactRedux},function(t,e){t.exports=Redux},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(module,exports,__webpack_require__){(function(jQuery){
|
||||||
!function(t,e){function n(e,n){var i,o,a,s=e.nodeName.toLowerCase()
|
!function(t,e){function n(e,n){var i,o,a,s=e.nodeName.toLowerCase()
|
||||||
return"area"===s?(i=e.parentNode,o=i.name,!(!e.href||!o||"map"!==i.nodeName.toLowerCase())&&(a=t("img[usemap=#"+o+"]")[0],!!a&&r(a))):(/input|select|textarea|button|object/.test(s)?!e.disabled:"a"===s?e.href||n:n)&&r(e)
|
return"area"===s?(i=e.parentNode,o=i.name,!(!e.href||!o||"map"!==i.nodeName.toLowerCase())&&(a=t("img[usemap=#"+o+"]")[0],!!a&&r(a))):(/input|select|textarea|button|object/.test(s)?!e.disabled:"a"===s?e.href||n:n)&&r(e)
|
||||||
|
|
||||||
@ -3362,7 +3362,7 @@ c.canUseDOM?void 0:s["default"](!1)
|
|||||||
var v=m.forceRefresh,g=d.supportsHistory(),y=!g||v,_=h["default"](o({},m,{getCurrentLocation:t,finishTransition:n,saveState:f.saveState})),b=0,x=void 0
|
var v=m.forceRefresh,g=d.supportsHistory(),y=!g||v,_=h["default"](o({},m,{getCurrentLocation:t,finishTransition:n,saveState:f.saveState})),b=0,x=void 0
|
||||||
return o({},_,{listenBefore:r,listen:i,registerTransitionHook:a,unregisterTransitionHook:p})}e.__esModule=!0
|
return o({},_,{listenBefore:r,listen:i,registerTransitionHook:a,unregisterTransitionHook:p})}e.__esModule=!0
|
||||||
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(201),s=r(a),l=n(202),u=n(203),c=n(205),d=n(206),f=n(207),p=n(208),h=r(p)
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(202),s=r(a),l=n(203),u=n(204),c=n(206),d=n(207),f=n(208),p=n(209),h=r(p)
|
||||||
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
var r=function(t,e,n,r,i,o,a,s){if(!t){var l
|
var r=function(t,e,n,r,i,o,a,s){if(!t){var l
|
||||||
if(void 0===e)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.")
|
if(void 0===e)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.")
|
||||||
@ -3381,7 +3381,7 @@ return null==e?t:t.substring(e[0].length)}function o(t){var e=i(t),n="",r="",o=e
|
|||||||
o!==-1&&(r=e.substring(o),e=e.substring(0,o))
|
o!==-1&&(r=e.substring(o),e=e.substring(0,o))
|
||||||
var a=e.indexOf("?")
|
var a=e.indexOf("?")
|
||||||
return a!==-1&&(n=e.substring(a),e=e.substring(0,a)),""===e&&(e="/"),{pathname:e,search:n,hash:r}}e.__esModule=!0,e.extractPath=i,e.parsePath=o
|
return a!==-1&&(n=e.substring(a),e=e.substring(0,a)),""===e&&(e="/"),{pathname:e,search:n,hash:r}}e.__esModule=!0,e.extractPath=i,e.parsePath=o
|
||||||
var a=n(204),s=r(a)},function(t,e,n){"use strict"
|
var a=n(205),s=r(a)},function(t,e,n){"use strict"
|
||||||
var r=function(){}
|
var r=function(){}
|
||||||
t.exports=r},function(t,e){"use strict"
|
t.exports=r},function(t,e){"use strict"
|
||||||
e.__esModule=!0
|
e.__esModule=!0
|
||||||
@ -3403,12 +3403,12 @@ if(n.name===d)return
|
|||||||
if(c.indexOf(n.name)>=0&&0===window.sessionStorage.length)return
|
if(c.indexOf(n.name)>=0&&0===window.sessionStorage.length)return
|
||||||
throw n}}function a(t){var e=void 0
|
throw n}}function a(t){var e=void 0
|
||||||
try{e=window.sessionStorage.getItem(i(t))}catch(n){if(n.name===d)return null}if(e)try{return JSON.parse(e)}catch(n){}return null}e.__esModule=!0,e.saveState=o,e.readState=a
|
try{e=window.sessionStorage.getItem(i(t))}catch(n){if(n.name===d)return null}if(e)try{return JSON.parse(e)}catch(n){}return null}e.__esModule=!0,e.saveState=o,e.readState=a
|
||||||
var s=n(204),l=r(s),u="@@History/",c=["QuotaExceededError","QUOTA_EXCEEDED_ERR"],d="SecurityError"},function(t,e,n){"use strict"
|
var s=n(205),l=r(s),u="@@History/",c=["QuotaExceededError","QUOTA_EXCEEDED_ERR"],d="SecurityError"},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){function e(t){return l.canUseDOM?void 0:s["default"](!1),n.listen(t)}var n=d["default"](o({getUserConfirmation:u.getUserConfirmation},t,{
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){function e(t){return l.canUseDOM?void 0:s["default"](!1),n.listen(t)}var n=d["default"](o({getUserConfirmation:u.getUserConfirmation},t,{
|
||||||
go:u.go}))
|
go:u.go}))
|
||||||
return o({},n,{listen:e})}e.__esModule=!0
|
return o({},n,{listen:e})}e.__esModule=!0
|
||||||
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(201),s=r(a),l=n(205),u=n(206),c=n(209),d=r(c)
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(202),s=r(a),l=n(206),u=n(207),c=n(210),d=r(c)
|
||||||
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return Math.random().toString(36).substr(2,t)}function o(t,e){return t.pathname===e.pathname&&t.search===e.search&&t.key===e.key&&d["default"](t.state,e.state)
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return Math.random().toString(36).substr(2,t)}function o(t,e){return t.pathname===e.pathname&&t.search===e.search&&t.key===e.key&&d["default"](t.state,e.state)
|
||||||
|
|
||||||
@ -3434,7 +3434,7 @@ return{listenBefore:t,listen:r,transitionTo:l,push:u,replace:c,go:I,goBack:m,goF
|
|||||||
registerTransitionHook:b["default"](E,"registerTransitionHook is deprecated; use listenBefore instead"),unregisterTransitionHook:b["default"](S,"unregisterTransitionHook is deprecated; use the callback returned from listenBefore instead"),
|
registerTransitionHook:b["default"](E,"registerTransitionHook is deprecated; use listenBefore instead"),unregisterTransitionHook:b["default"](S,"unregisterTransitionHook is deprecated; use the callback returned from listenBefore instead"),
|
||||||
pushState:b["default"](P,"pushState is deprecated; use push instead"),replaceState:b["default"](O,"replaceState is deprecated; use replace instead")}}e.__esModule=!0
|
pushState:b["default"](P,"pushState is deprecated; use push instead"),replaceState:b["default"](O,"replaceState is deprecated; use replace instead")}}e.__esModule=!0
|
||||||
var s=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var s=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},l=n(204),u=r(l),c=n(210),d=r(c),f=n(203),p=n(213),h=n(202),m=n(214),v=r(m),g=n(215),y=r(g),_=n(216),b=r(_),x=6
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},l=n(205),u=r(l),c=n(211),d=r(c),f=n(204),p=n(214),h=n(203),m=n(215),v=r(m),g=n(216),y=r(g),_=n(217),b=r(_),x=6
|
||||||
e["default"]=a,t.exports=e["default"]},function(t,e,n){function r(t){return null===t||void 0===t}function i(t){return!(!t||"object"!=typeof t||"number"!=typeof t.length)&&("function"==typeof t.copy&&"function"==typeof t.slice&&!(t.length>0&&"number"!=typeof t[0]))
|
e["default"]=a,t.exports=e["default"]},function(t,e,n){function r(t){return null===t||void 0===t}function i(t){return!(!t||"object"!=typeof t||"number"!=typeof t.length)&&("function"==typeof t.copy&&"function"==typeof t.slice&&!(t.length>0&&"number"!=typeof t[0]))
|
||||||
|
|
||||||
}function o(t,e,n){var o,c
|
}function o(t,e,n){var o,c
|
||||||
@ -3447,7 +3447,7 @@ for(o=0;o<t.length;o++)if(t[o]!==e[o])return!1
|
|||||||
return!0}try{var d=s(t),f=s(e)}catch(p){return!1}if(d.length!=f.length)return!1
|
return!0}try{var d=s(t),f=s(e)}catch(p){return!1}if(d.length!=f.length)return!1
|
||||||
for(d.sort(),f.sort(),o=d.length-1;o>=0;o--)if(d[o]!=f[o])return!1
|
for(d.sort(),f.sort(),o=d.length-1;o>=0;o--)if(d[o]!=f[o])return!1
|
||||||
for(o=d.length-1;o>=0;o--)if(c=d[o],!u(t[c],e[c],n))return!1
|
for(o=d.length-1;o>=0;o--)if(c=d[o],!u(t[c],e[c],n))return!1
|
||||||
return typeof t==typeof e}var a=Array.prototype.slice,s=n(211),l=n(212),u=t.exports=function(t,e,n){return n||(n={}),t===e||(t instanceof Date&&e instanceof Date?t.getTime()===e.getTime():!t||!e||"object"!=typeof t&&"object"!=typeof e?n.strict?t===e:t==e:o(t,e,n))
|
return typeof t==typeof e}var a=Array.prototype.slice,s=n(212),l=n(213),u=t.exports=function(t,e,n){return n||(n={}),t===e||(t instanceof Date&&e instanceof Date?t.getTime()===e.getTime():!t||!e||"object"!=typeof t&&"object"!=typeof e?n.strict?t===e:t==e:o(t,e,n))
|
||||||
|
|
||||||
}},function(t,e){function n(t){var e=[]
|
}},function(t,e){function n(t){var e=[]
|
||||||
for(var n in t)e.push(n)
|
for(var n in t)e.push(n)
|
||||||
@ -3467,15 +3467,15 @@ function r(t){return t&&t.__esModule?t:{"default":t}}function i(){var t=argument
|
|||||||
var i=t.pathname||"/",a=t.search||"",s=t.hash||"",c=t.state||null
|
var i=t.pathname||"/",a=t.search||"",s=t.hash||"",c=t.state||null
|
||||||
return{pathname:i,search:a,hash:s,state:c,action:e,key:n}}e.__esModule=!0
|
return{pathname:i,search:a,hash:s,state:c,action:e,key:n}}e.__esModule=!0
|
||||||
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(204),s=r(a),l=n(202),u=n(203)
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(205),s=r(a),l=n(203),u=n(204)
|
||||||
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e,n){var r=t(e,n)
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e,n){var r=t(e,n)
|
||||||
t.length<2&&n(r)}e.__esModule=!0
|
t.length<2&&n(r)}e.__esModule=!0
|
||||||
var o=n(204),a=r(o)
|
var o=n(205),a=r(o)
|
||||||
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return function(){return t.apply(this,arguments)}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return function(){return t.apply(this,arguments)}}e.__esModule=!0
|
||||||
var o=n(204),a=r(o)
|
var o=n(205),a=r(o)
|
||||||
e["default"]=i,t.exports=e["default"]},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(t,e,n){(function(t){"use strict"
|
e["default"]=i,t.exports=e["default"]},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(t,e,n){(function(t){"use strict"
|
||||||
function e(t,e,n){t[e]||Object[r](t,e,{writable:!0,configurable:!0,value:n})}if(n(262),n(553),n(554),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed")
|
function e(t,e,n){t[e]||Object[r](t,e,{writable:!0,configurable:!0,value:n})}if(n(262),n(553),n(554),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed")
|
||||||
t._babelPolyfill=!0
|
t._babelPolyfill=!0
|
||||||
var r="defineProperty"
|
var r="defineProperty"
|
||||||
@ -7502,7 +7502,7 @@ var E=T.queryKey;(void 0===E||E)&&(E="string"==typeof E?E:w)
|
|||||||
var S=x["default"](c({},T,{getCurrentLocation:t,finishTransition:n,saveState:_.saveState})),P=0,O=void 0,M=y.supportsGoWithoutReloadUsingHash()
|
var S=x["default"](c({},T,{getCurrentLocation:t,finishTransition:n,saveState:_.saveState})),P=0,O=void 0,M=y.supportsGoWithoutReloadUsingHash()
|
||||||
return c({},S,{listenBefore:r,listen:i,push:u,replace:d,go:f,createHref:p,registerTransitionHook:b,unregisterTransitionHook:k,pushState:C,replaceState:j})}e.__esModule=!0
|
return c({},S,{listenBefore:r,listen:i,push:u,replace:d,go:f,createHref:p,registerTransitionHook:b,unregisterTransitionHook:k,pushState:C,replaceState:j})}e.__esModule=!0
|
||||||
var c=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var c=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},d=n(204),f=r(d),p=n(201),h=r(p),m=n(202),v=n(203),g=n(205),y=n(206),_=n(207),b=n(208),x=r(b),w="_k"
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},d=n(205),f=r(d),p=n(202),h=r(p),m=n(203),v=n(204),g=n(206),y=n(207),_=n(208),b=n(209),x=r(b),w="_k"
|
||||||
e["default"]=u,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=u,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return c.stringify(t).replace(/%20/g,"+")}function o(t){for(var e in t)if(Object.prototype.hasOwnProperty.call(t,e)&&"object"==typeof t[e]&&!Array.isArray(t[e])&&null!==t[e])return!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return c.stringify(t).replace(/%20/g,"+")}function o(t){for(var e in t)if(Object.prototype.hasOwnProperty.call(t,e)&&"object"==typeof t[e]&&!Array.isArray(t[e])&&null!==t[e])return!0
|
||||||
|
|
||||||
@ -7525,7 +7525,7 @@ return t.query&&(a.query=t.query),e(a)}function h(t,e,n){"string"==typeof e&&(e=
|
|||||||
return"function"!=typeof x&&(x=i),"function"!=typeof w&&(w=g),s({},b,{listenBefore:r,listen:o,push:a,replace:l,createPath:u,createHref:c,createLocation:d,pushState:m["default"](h,"pushState is deprecated; use push instead"),
|
return"function"!=typeof x&&(x=i),"function"!=typeof w&&(w=g),s({},b,{listenBefore:r,listen:o,push:a,replace:l,createPath:u,createHref:c,createLocation:d,pushState:m["default"](h,"pushState is deprecated; use push instead"),
|
||||||
replaceState:m["default"](y,"replaceState is deprecated; use replace instead")})}}e.__esModule=!0
|
replaceState:m["default"](y,"replaceState is deprecated; use replace instead")})}}e.__esModule=!0
|
||||||
var s=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var s=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},l=n(204),u=r(l),c=n(945),d=n(215),f=r(d),p=n(203),h=n(216),m=r(h),v="$searchBase",g=c.parse
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},l=n(205),u=r(l),c=n(945),d=n(216),f=r(d),p=n(204),h=n(217),m=r(h),v="$searchBase",g=c.parse
|
||||||
e["default"]=a,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=a,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
var r=n(946)
|
var r=n(946)
|
||||||
e.extract=function(t){return t.split("?")[1]||""},e.parse=function(t){return"string"!=typeof t?{}:(t=t.trim().replace(/^(\?|#|&)/,""),t?t.split("&").reduce(function(t,e){var n=e.replace(/\+/g," ").split("="),r=n.shift(),i=n.length>0?n.join("="):void 0
|
e.extract=function(t){return t.split("?")[1]||""},e.parse=function(t){return"string"!=typeof t?{}:(t=t.trim().replace(/^(\?|#|&)/,""),t?t.split("&").reduce(function(t,e){var n=e.replace(/\+/g," ").split("="),r=n.shift(),i=n.length>0?n.join("="):void 0
|
||||||
@ -7782,7 +7782,7 @@ n=(0,m.createRoutingHistory)(n,c),c.match(o,function(t,r,i){e(t,r&&v.createLocat
|
|||||||
|
|
||||||
|
|
||||||
var a=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var a=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},s=n(202),l=n(941),u=r(l),c=n(972),d=r(c),f=n(947),p=r(f),h=n(934),m=n(957)
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},s=n(203),l=n(941),u=r(l),c=n(972),d=r(c),f=n(947),p=r(f),h=n(934),m=n(957)
|
||||||
e["default"]=o,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=o,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){var e=(0,c["default"])(t),n=function i(){return e},r=(0,a["default"])((0,l["default"])(n))(t)
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){var e=(0,c["default"])(t),n=function i(){return e},r=(0,a["default"])((0,l["default"])(n))(t)
|
||||||
return r.__v2_compatible__=!0,r}e.__esModule=!0,e["default"]=i
|
return r.__v2_compatible__=!0,r}e.__esModule=!0,e["default"]=i
|
||||||
@ -7805,7 +7805,7 @@ return n(_.createLocation.apply(_,[r(t)].concat(i)))}function v(t,e){"string"==t
|
|||||||
return o({},_,{listenBefore:i,listen:a,push:s,replace:c,createPath:f,createHref:h,createLocation:m,pushState:p["default"](v,"pushState is deprecated; use push instead"),replaceState:p["default"](g,"replaceState is deprecated; use replace instead")
|
return o({},_,{listenBefore:i,listen:a,push:s,replace:c,createPath:f,createHref:h,createLocation:m,pushState:p["default"](v,"pushState is deprecated; use push instead"),replaceState:p["default"](g,"replaceState is deprecated; use replace instead")
|
||||||
})}}e.__esModule=!0
|
})}}e.__esModule=!0
|
||||||
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(204),s=r(a),l=n(205),u=n(203),c=n(215),d=r(c),f=n(216),p=r(f)
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},a=n(205),s=r(a),l=n(206),u=n(204),c=n(216),d=r(c),f=n(217),p=r(f)
|
||||||
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=i,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return t.filter(function(t){return t.state}).reduce(function(t,e){return t[e.key]=e.state,t},{})}function o(){function t(t,e){g[t]=e}function e(t){
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return t.filter(function(t){return t.state}).reduce(function(t,e){return t[e.key]=e.state,t},{})}function o(){function t(t,e){g[t]=e}function e(t){
|
||||||
return g[t]}function n(){var t=m[v],n=t.basename,r=t.pathname,i=t.search,o=(n||"")+r+(i||""),s=void 0,l=void 0
|
return g[t]}function n(){var t=m[v],n=t.basename,r=t.pathname,i=t.search,o=(n||"")+r+(i||""),s=void 0,l=void 0
|
||||||
@ -7825,7 +7825,7 @@ return"string"==typeof t?{pathname:t,key:e}:"object"==typeof t&&t?a({},t,{key:e}
|
|||||||
var g=i(m)
|
var g=i(m)
|
||||||
return u}e.__esModule=!0
|
return u}e.__esModule=!0
|
||||||
var a=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var a=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},s=n(204),l=r(s),u=n(201),c=r(u),d=n(203),f=n(202),p=n(209),h=r(p)
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},s=n(205),l=r(s),u=n(202),c=r(u),d=n(204),f=n(203),p=n(210),h=r(p)
|
||||||
e["default"]=o,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=o,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return function(e){var n=(0,a["default"])((0,l["default"])(t))(e)
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return function(e){var n=(0,a["default"])((0,l["default"])(t))(e)
|
||||||
return n.__v2_compatible__=!0,n}}e.__esModule=!0,e["default"]=i
|
return n.__v2_compatible__=!0,n}}e.__esModule=!0,e["default"]=i
|
||||||
@ -7843,7 +7843,7 @@ return function(t){return r.reduceRight(function(e,n){return n(e,t)},a["default"
|
|||||||
|
|
||||||
|
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
||||||
var i=n(200),o=r(i),a=n(978),s=r(a)
|
var i=n(201),o=r(i),a=n(978),s=r(a)
|
||||||
e["default"]=(0,s["default"])(o["default"]),t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=(0,s["default"])(o["default"]),t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0,e["default"]=function(t){var e=void 0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0,e["default"]=function(t){var e=void 0
|
||||||
return a&&(e=(0,o["default"])(t)()),e}
|
return a&&(e=(0,o["default"])(t)()),e}
|
||||||
@ -8325,7 +8325,7 @@ s["default"])(t,["componentClass","className"]),i=(0,b.splitBsProps)(r),a=i[0],l
|
|||||||
return g["default"].createElement(e,(0,o["default"])({},l,{className:(0,m["default"])(n,u)}))},e}(g["default"].Component)
|
return g["default"].createElement(e,(0,o["default"])({},l,{className:(0,m["default"])(n,u)}))},e}(g["default"].Component)
|
||||||
k.propTypes=x,k.defaultProps=w,e["default"]=(0,b.bsClass)("carousel-caption",k),t.exports=e["default"]},function(t,e,n){"use strict"
|
k.propTypes=x,k.defaultProps=w,e["default"]=(0,b.bsClass)("carousel-caption",k),t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
||||||
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(1074),m=r(h),v=n(5),g=r(v),y=n(176),_=r(y),b=n(1101),x=r(b),w={direction:g["default"].PropTypes.oneOf(["prev","next"]),
|
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(1074),m=r(h),v=n(5),g=r(v),y=n(177),_=r(y),b=n(1101),x=r(b),w={direction:g["default"].PropTypes.oneOf(["prev","next"]),
|
||||||
onAnimateOutEnd:g["default"].PropTypes.func,active:g["default"].PropTypes.bool,animateIn:g["default"].PropTypes.bool,animateOut:g["default"].PropTypes.bool,index:g["default"].PropTypes.number},k={active:!1,
|
onAnimateOutEnd:g["default"].PropTypes.func,active:g["default"].PropTypes.bool,animateIn:g["default"].PropTypes.bool,animateOut:g["default"].PropTypes.bool,index:g["default"].PropTypes.number},k={active:!1,
|
||||||
animateIn:!1,animateOut:!1},C=function(t){function e(n,r){(0,u["default"])(this,e)
|
animateIn:!1,animateOut:!1},C=function(t){function e(n,r){(0,u["default"])(this,e)
|
||||||
var i=(0,d["default"])(this,t.call(this,n,r))
|
var i=(0,d["default"])(this,t.call(this,n,r))
|
||||||
@ -8458,7 +8458,7 @@ t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,wri
|
|||||||
value:!0}),e.EXITING=e.ENTERED=e.ENTERING=e.EXITED=e.UNMOUNTED=void 0
|
value:!0}),e.EXITING=e.ENTERED=e.ENTERING=e.EXITED=e.UNMOUNTED=void 0
|
||||||
var u=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var u=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},c=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n]
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},c=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n]
|
||||||
r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),d=n(5),f=r(d),p=n(176),h=r(p),m=n(1119),v=r(m),g=n(1121),y=r(g),_=n(1074),b=r(_),x=v["default"].end,w=e.UNMOUNTED=0,k=e.EXITED=1,C=e.ENTERING=2,j=e.ENTERED=3,T=e.EXITING=4,E=function(t){
|
r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),d=n(5),f=r(d),p=n(177),h=r(p),m=n(1119),v=r(m),g=n(1121),y=r(g),_=n(1074),b=r(_),x=v["default"].end,w=e.UNMOUNTED=0,k=e.EXITED=1,C=e.ENTERING=2,j=e.ENTERED=3,T=e.EXITING=4,E=function(t){
|
||||||
function e(t,n){o(this,e)
|
function e(t,n){o(this,e)
|
||||||
var r=a(this,Object.getPrototypeOf(e).call(this,t,n)),i=void 0
|
var r=a(this,Object.getPrototypeOf(e).call(this,t,n)),i=void 0
|
||||||
return i=t["in"]?t.transitionAppear?k:j:t.unmountOnExit?w:k,r.state={status:i},r.nextCallback=null,r}return s(e,t),c(e,[{key:"componentDidMount",value:function n(){this.props.transitionAppear&&this.props["in"]&&this.performEnter(this.props)
|
return i=t["in"]?t.transitionAppear?k:j:t.unmountOnExit?w:k,r.state={status:i},r.nextCallback=null,r}return s(e,t),c(e,[{key:"componentDidMount",value:function n(){this.props.transitionAppear&&this.props["in"]&&this.performEnter(this.props)
|
||||||
@ -8500,7 +8500,7 @@ var r=n(1120),i=function o(){}
|
|||||||
r&&(i=function(){return document.addEventListener?function(t,e,n,r){return t.addEventListener(e,n,r||!1)}:document.attachEvent?function(t,e,n){return t.attachEvent("on"+e,n)}:void 0}()),t.exports=i},function(t,e,n){
|
r&&(i=function(){return document.addEventListener?function(t,e,n,r){return t.addEventListener(e,n,r||!1)}:document.attachEvent?function(t,e,n){return t.attachEvent("on"+e,n)}:void 0}()),t.exports=i},function(t,e,n){
|
||||||
"use strict"
|
"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
||||||
var i=n(1073),o=r(i),a=n(989),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(1074),m=r(h),v=n(1123),g=r(v),y=n(1125),_=r(y),b=n(1126),x=r(b),w=n(5),k=r(w),C=n(176),j=r(C),T=n(1096),E=r(T),S=n(1092),P=r(S),O=n(1127),M=r(O),N=n(1128),A=r(N),D=n(1104),I=r(D),R=n(1095),F=r(R),L=n(1131),H=r(L),Q=n(1146),z=r(Q),W=n(1075),B=n(1082),U=r(B),q=n(1147),$=n(1083),V=r($),K=z["default"].defaultProps.bsRole,X=H["default"].defaultProps.bsRole,Y={
|
var i=n(1073),o=r(i),a=n(989),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(1074),m=r(h),v=n(1123),g=r(v),y=n(1125),_=r(y),b=n(1126),x=r(b),w=n(5),k=r(w),C=n(177),j=r(C),T=n(1096),E=r(T),S=n(1092),P=r(S),O=n(1127),M=r(O),N=n(1128),A=r(N),D=n(1104),I=r(D),R=n(1095),F=r(R),L=n(1131),H=r(L),Q=n(1146),z=r(Q),W=n(1075),B=n(1082),U=r(B),q=n(1147),$=n(1083),V=r($),K=z["default"].defaultProps.bsRole,X=H["default"].defaultProps.bsRole,Y={
|
||||||
dropup:k["default"].PropTypes.bool,id:(0,M["default"])(k["default"].PropTypes.oneOfType([k["default"].PropTypes.string,k["default"].PropTypes.number])),componentClass:P["default"],children:(0,E["default"])((0,
|
dropup:k["default"].PropTypes.bool,id:(0,M["default"])(k["default"].PropTypes.oneOfType([k["default"].PropTypes.string,k["default"].PropTypes.number])),componentClass:P["default"],children:(0,E["default"])((0,
|
||||||
q.requiredRoles)(K,X),(0,q.exclusiveRoles)(X)),disabled:k["default"].PropTypes.bool,pullRight:k["default"].PropTypes.bool,open:k["default"].PropTypes.bool,onClose:k["default"].PropTypes.func,onToggle:k["default"].PropTypes.func,
|
q.requiredRoles)(K,X),(0,q.exclusiveRoles)(X)),disabled:k["default"].PropTypes.bool,pullRight:k["default"].PropTypes.bool,open:k["default"].PropTypes.bool,onClose:k["default"].PropTypes.func,onToggle:k["default"].PropTypes.func,
|
||||||
onSelect:k["default"].PropTypes.func,role:k["default"].PropTypes.string},G={componentClass:F["default"]},Z=function(t){function e(n,r){(0,u["default"])(this,e)
|
onSelect:k["default"].PropTypes.func,role:k["default"].PropTypes.string},G={componentClass:F["default"]},Z=function(t){function e(n,r){(0,u["default"])(this,e)
|
||||||
@ -8609,7 +8609,7 @@ for(var r in t)h(t,r)&&e.call(n,t[r],r,t)}function h(t,e){return!!t&&Object.prot
|
|||||||
e.uncontrolledPropTypes=o,e.getType=a,e.getValue=s,e.getLinkName=u,e.defaultKey=c,e.chain=d,e.transform=f,e.each=p,e.has=h,e.isReactComponent=m
|
e.uncontrolledPropTypes=o,e.getType=a,e.getValue=s,e.getLinkName=u,e.defaultKey=c,e.chain=d,e.transform=f,e.each=p,e.has=h,e.isReactComponent=m
|
||||||
var v=n(5),g=r(v),y=n(1080),_=r(y),b=e.version=g["default"].version.split(".").map(parseFloat)},function(t,e,n){"use strict"
|
var v=n(5),g=r(v),y=n(1080),_=r(y),b=e.version=g["default"].version.split(".").map(parseFloat)},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
||||||
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1132),u=r(l),c=n(1027),d=r(c),f=n(1028),p=r(f),h=n(1064),m=r(h),v=n(1074),g=r(v),y=n(1126),_=r(y),b=n(5),x=r(b),w=n(176),k=r(w),C=n(1141),j=r(C),T=n(1075),E=n(1082),S=r(E),P=n(1083),O=r(P),M={
|
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1132),u=r(l),c=n(1027),d=r(c),f=n(1028),p=r(f),h=n(1064),m=r(h),v=n(1074),g=r(v),y=n(1126),_=r(y),b=n(5),x=r(b),w=n(177),k=r(w),C=n(1141),j=r(C),T=n(1075),E=n(1082),S=r(E),P=n(1083),O=r(P),M={
|
||||||
open:x["default"].PropTypes.bool,pullRight:x["default"].PropTypes.bool,onClose:x["default"].PropTypes.func,labelledBy:x["default"].PropTypes.oneOfType([x["default"].PropTypes.string,x["default"].PropTypes.number]),
|
open:x["default"].PropTypes.bool,pullRight:x["default"].PropTypes.bool,onClose:x["default"].PropTypes.func,labelledBy:x["default"].PropTypes.oneOfType([x["default"].PropTypes.string,x["default"].PropTypes.number]),
|
||||||
onSelect:x["default"].PropTypes.func},N={bsRole:"menu",pullRight:!1},A=function(t){function e(n){(0,d["default"])(this,e)
|
onSelect:x["default"].PropTypes.func},N={bsRole:"menu",pullRight:!1},A=function(t){function e(n){(0,d["default"])(this,e)
|
||||||
var r=(0,p["default"])(this,t.call(this,n))
|
var r=(0,p["default"])(this,t.call(this,n))
|
||||||
@ -8662,7 +8662,7 @@ t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,wri
|
|||||||
}function u(t){return!!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)}function c(){var t=w+"_"+k++
|
}function u(t){return!!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)}function c(){var t=w+"_"+k++
|
||||||
return{id:t,suppressRootClose:function e(n){n.nativeEvent[t]=!0}}}Object.defineProperty(e,"__esModule",{value:!0})
|
return{id:t,suppressRootClose:function e(n){n.nativeEvent[t]=!0}}}Object.defineProperty(e,"__esModule",{value:!0})
|
||||||
var d=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n]
|
var d=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n]
|
||||||
r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),f=n(5),p=r(f),h=n(176),m=r(h),v=n(1142),g=r(v),y=n(1144),_=r(y),b=n(1145),x=r(b),w="__click_was_inside",k=0,C=function(t){
|
r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),f=n(5),p=r(f),h=n(177),m=r(h),v=n(1142),g=r(v),y=n(1144),_=r(y),b=n(1145),x=r(b),w="__click_was_inside",k=0,C=function(t){
|
||||||
function e(t){o(this,e)
|
function e(t){o(this,e)
|
||||||
var n=a(this,Object.getPrototypeOf(e).call(this,t))
|
var n=a(this,Object.getPrototypeOf(e).call(this,t))
|
||||||
n.handleDocumentMouse=n.handleDocumentMouse.bind(n),n.handleDocumentKeyUp=n.handleDocumentKeyUp.bind(n)
|
n.handleDocumentMouse=n.handleDocumentMouse.bind(n),n.handleDocumentKeyUp=n.handleDocumentKeyUp.bind(n)
|
||||||
@ -8693,7 +8693,7 @@ return e.filter(function(t){return null!=t}).reduce(function(t,e){if("function"!
|
|||||||
return null===t?e:function n(){for(var n=arguments.length,r=Array(n),i=0;i<n;i++)r[i]=arguments[i]
|
return null===t?e:function n(){for(var n=arguments.length,r=Array(n),i=0;i<n;i++)r[i]=arguments[i]
|
||||||
t.apply(this,r),e.apply(this,r)}},null)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict"
|
t.apply(this,r),e.apply(this,r)}},null)}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=n,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=function(t){return(0,s["default"])(o["default"].findDOMNode(t))}
|
function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=function(t){return(0,s["default"])(o["default"].findDOMNode(t))}
|
||||||
var i=n(176),o=r(i),a=n(1124),s=r(a)
|
var i=n(177),o=r(i),a=n(1124),s=r(a)
|
||||||
t.exports=e["default"]},function(t,e,n){"use strict"
|
t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
||||||
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(5),m=r(h),v=n(1074),g=r(v),y=n(1094),_=r(y),b=n(1091),x=r(b),w=n(1075),k={noCaret:m["default"].PropTypes.bool,
|
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(5),m=r(h),v=n(1074),g=r(v),y=n(1094),_=r(y),b=n(1091),x=r(b),w=n(1075),k={noCaret:m["default"].PropTypes.bool,
|
||||||
@ -8907,7 +8907,7 @@ role:"heading",className:(0,m["default"])(l,(0,w.prefix)(f,"header")),style:u}))
|
|||||||
o["default"])({},p,{role:"menuitem",tabIndex:"-1",onClick:(0,C["default"])(a,this.handleClick)})))},e}(g["default"].Component)
|
o["default"])({},p,{role:"menuitem",tabIndex:"-1",onClick:(0,C["default"])(a,this.handleClick)})))},e}(g["default"].Component)
|
||||||
E.propTypes=j,E.defaultProps=T,e["default"]=(0,w.bsClass)("dropdown",E),t.exports=e["default"]},function(t,e,n){"use strict"
|
E.propTypes=j,E.defaultProps=T,e["default"]=(0,w.bsClass)("dropdown",E),t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
||||||
var i=n(1073),o=r(i),a=n(1027),s=r(a),l=n(1028),u=r(l),c=n(1064),d=r(c),f=n(989),p=r(f),h=n(1074),m=r(h),v=n(1175),g=r(v),y=n(1124),_=r(y),b=n(1120),x=r(b),w=n(1178),k=r(w),C=n(5),j=r(C),T=n(176),E=r(T),S=n(1179),P=r(S),O=n(1188),M=r(O),N=n(1092),A=r(N),D=n(1150),I=r(D),R=n(1192),F=r(R),L=n(1193),H=r(L),Q=n(1194),z=r(Q),W=n(1195),B=r(W),U=n(1196),q=r(U),$=n(1075),V=n(1082),K=r(V),X=n(1149),Y=r(X),G=n(1081),Z=(0,
|
var i=n(1073),o=r(i),a=n(1027),s=r(a),l=n(1028),u=r(l),c=n(1064),d=r(c),f=n(989),p=r(f),h=n(1074),m=r(h),v=n(1175),g=r(v),y=n(1124),_=r(y),b=n(1120),x=r(b),w=n(1178),k=r(w),C=n(5),j=r(C),T=n(177),E=r(T),S=n(1179),P=r(S),O=n(1188),M=r(O),N=n(1092),A=r(N),D=n(1150),I=r(D),R=n(1192),F=r(R),L=n(1193),H=r(L),Q=n(1194),z=r(Q),W=n(1195),B=r(W),U=n(1196),q=r(U),$=n(1075),V=n(1082),K=r(V),X=n(1149),Y=r(X),G=n(1081),Z=(0,
|
||||||
p["default"])({},P["default"].propTypes,H["default"].propTypes,{backdrop:j["default"].PropTypes.oneOf(["static",!0,!1]),keyboard:j["default"].PropTypes.bool,animation:j["default"].PropTypes.bool,dialogComponentClass:A["default"],
|
p["default"])({},P["default"].propTypes,H["default"].propTypes,{backdrop:j["default"].PropTypes.oneOf(["static",!0,!1]),keyboard:j["default"].PropTypes.bool,animation:j["default"].PropTypes.bool,dialogComponentClass:A["default"],
|
||||||
autoFocus:j["default"].PropTypes.bool,enforceFocus:j["default"].PropTypes.bool,show:j["default"].PropTypes.bool,onHide:j["default"].PropTypes.func,onEnter:j["default"].PropTypes.func,onEntering:j["default"].PropTypes.func,
|
autoFocus:j["default"].PropTypes.bool,enforceFocus:j["default"].PropTypes.bool,show:j["default"].PropTypes.bool,onHide:j["default"].PropTypes.func,onEnter:j["default"].PropTypes.func,onEntering:j["default"].PropTypes.func,
|
||||||
onEntered:j["default"].PropTypes.func,onExit:j["default"].PropTypes.func,onExiting:j["default"].PropTypes.func,onExited:j["default"].PropTypes.func,container:P["default"].propTypes.container}),J=(0,p["default"])({},P["default"].defaultProps,{
|
onEntered:j["default"].PropTypes.func,onExit:j["default"].PropTypes.func,onExiting:j["default"].PropTypes.func,onExited:j["default"].PropTypes.func,container:P["default"].propTypes.container}),J=(0,p["default"])({},P["default"].defaultProps,{
|
||||||
@ -8988,7 +8988,7 @@ var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){re
|
|||||||
|
|
||||||
e["default"]=(0,u["default"])(i)},function(t,e,n){"use strict"
|
e["default"]=(0,u["default"])(i)},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0})
|
function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0})
|
||||||
var i=n(5),o=r(i),a=n(176),s=r(a),l=n(1180),u=r(l),c=n(1145),d=r(c),f=n(1182),p=r(f),h=o["default"].createClass({displayName:"Portal",propTypes:{container:o["default"].PropTypes.oneOfType([u["default"],o["default"].PropTypes.func])
|
var i=n(5),o=r(i),a=n(177),s=r(a),l=n(1180),u=r(l),c=n(1145),d=r(c),f=n(1182),p=r(f),h=o["default"].createClass({displayName:"Portal",propTypes:{container:o["default"].PropTypes.oneOfType([u["default"],o["default"].PropTypes.func])
|
||||||
},componentDidMount:function m(){this._renderOverlay()},componentDidUpdate:function v(){this._renderOverlay()},componentWillReceiveProps:function g(t){this._overlayTarget&&t.container!==this.props.container&&(this._portalContainerNode.removeChild(this._overlayTarget),
|
},componentDidMount:function m(){this._renderOverlay()},componentDidUpdate:function v(){this._renderOverlay()},componentWillReceiveProps:function g(t){this._overlayTarget&&t.container!==this.props.container&&(this._portalContainerNode.removeChild(this._overlayTarget),
|
||||||
this._portalContainerNode=(0,p["default"])(t.container,(0,d["default"])(this).body),this._portalContainerNode.appendChild(this._overlayTarget))},componentWillUnmount:function y(){this._unrenderOverlay(),
|
this._portalContainerNode=(0,p["default"])(t.container,(0,d["default"])(this).body),this._portalContainerNode.appendChild(this._overlayTarget))},componentWillUnmount:function y(){this._unrenderOverlay(),
|
||||||
this._unmountOverlayTarget()},_mountOverlayTarget:function _(){this._overlayTarget||(this._overlayTarget=document.createElement("div"),this._portalContainerNode=(0,p["default"])(this.props.container,(0,
|
this._unmountOverlayTarget()},_mountOverlayTarget:function _(){this._overlayTarget||(this._overlayTarget=document.createElement("div"),this._portalContainerNode=(0,p["default"])(this.props.container,(0,
|
||||||
@ -9000,7 +9000,7 @@ if(!this.isMounted())throw new Error("getOverlayDOMNode(): A component must be m
|
|||||||
return this._overlayInstance?this._overlayInstance.getWrappedDOMNode?this._overlayInstance.getWrappedDOMNode():s["default"].findDOMNode(this._overlayInstance):null}})
|
return this._overlayInstance?this._overlayInstance.getWrappedDOMNode?this._overlayInstance.getWrappedDOMNode():s["default"].findDOMNode(this._overlayInstance):null}})
|
||||||
e["default"]=h,t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=h,t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return t="function"==typeof t?t():t,a["default"].findDOMNode(t)||e}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return t="function"==typeof t?t():t,a["default"].findDOMNode(t)||e}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=i
|
||||||
var o=n(176),a=r(o)
|
var o=n(177),a=r(o)
|
||||||
t.exports=e["default"]},function(t,e,n){"use strict"
|
t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){var n=-1
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){var n=-1
|
||||||
return t.some(function(t,r){if(e(t,r))return n=r,!0}),n}function a(t,e){return o(t,function(t){return t.modals.indexOf(e)!==-1})}Object.defineProperty(e,"__esModule",{value:!0})
|
return t.some(function(t,r){if(e(t,r))return n=r,!0}),n}function a(t,e){return o(t,function(t){return t.modals.indexOf(e)!==-1})}Object.defineProperty(e,"__esModule",{value:!0})
|
||||||
@ -9074,7 +9074,7 @@ var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064)
|
|||||||
return g["default"].createElement("h4",(0,o["default"])({},a,{className:(0,m["default"])(e,l)}))},e}(g["default"].Component)
|
return g["default"].createElement("h4",(0,o["default"])({},a,{className:(0,m["default"])(e,l)}))},e}(g["default"].Component)
|
||||||
e["default"]=(0,y.bsClass)("modal-title",_),t.exports=e["default"]},function(t,e,n){"use strict"
|
e["default"]=(0,y.bsClass)("modal-title",_),t.exports=e["default"]},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}e.__esModule=!0
|
||||||
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(1074),m=r(h),v=n(1126),g=r(v),y=n(5),_=r(y),b=n(176),x=r(b),w=n(1096),k=r(w),C=n(1104),j=r(C),T=n(1075),E=n(1082),S=r(E),P=n(1083),O=r(P),M={
|
var i=n(989),o=r(i),a=n(1073),s=r(a),l=n(1027),u=r(l),c=n(1028),d=r(c),f=n(1064),p=r(f),h=n(1074),m=r(h),v=n(1126),g=r(v),y=n(5),_=r(y),b=n(177),x=r(b),w=n(1096),k=r(w),C=n(1104),j=r(C),T=n(1075),E=n(1082),S=r(E),P=n(1083),O=r(P),M={
|
||||||
activeKey:_["default"].PropTypes.any,activeHref:_["default"].PropTypes.string,stacked:_["default"].PropTypes.bool,justified:(0,k["default"])(_["default"].PropTypes.bool,function(t){var e=t.justified,n=t.navbar
|
activeKey:_["default"].PropTypes.any,activeHref:_["default"].PropTypes.string,stacked:_["default"].PropTypes.bool,justified:(0,k["default"])(_["default"].PropTypes.bool,function(t){var e=t.justified,n=t.navbar
|
||||||
|
|
||||||
|
|
||||||
@ -9235,7 +9235,7 @@ t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,wri
|
|||||||
value:!0})
|
value:!0})
|
||||||
var l=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
var l=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]
|
||||||
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},u=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n]
|
for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t},u=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n]
|
||||||
r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),c=n(1074),d=r(c),f=n(5),p=r(f),h=n(176),m=r(h),v=n(1180),g=r(v),y=n(1208),_=r(y),b=n(1182),x=r(b),w=n(1145),k=r(w),C=function(t){
|
r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),c=n(1074),d=r(c),f=n(5),p=r(f),h=n(177),m=r(h),v=n(1180),g=r(v),y=n(1208),_=r(y),b=n(1182),x=r(b),w=n(1145),k=r(w),C=function(t){
|
||||||
function e(t,n){o(this,e)
|
function e(t,n){o(this,e)
|
||||||
var r=a(this,Object.getPrototypeOf(e).call(this,t,n))
|
var r=a(this,Object.getPrototypeOf(e).call(this,t,n))
|
||||||
return r.state={positionLeft:0,positionTop:0,arrowOffsetLeft:null,arrowOffsetTop:null},r._needsFlush=!1,r._lastTarget=null,r}return s(e,t),u(e,[{key:"componentDidMount",value:function n(){this.updatePosition(this.getTarget())
|
return r.state={positionLeft:0,positionTop:0,arrowOffsetLeft:null,arrowOffsetTop:null},r._needsFlush=!1,r._lastTarget=null,r}return s(e,t),u(e,[{key:"componentDidMount",value:function n(){this.updatePosition(this.getTarget())
|
||||||
@ -9296,7 +9296,7 @@ return void 0===e?n?"pageXOffset"in n?n.pageXOffset:n.document.documentElement.s
|
|||||||
|
|
||||||
}},function(t,e,n){"use strict"
|
}},function(t,e,n){"use strict"
|
||||||
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return Array.isArray(e)?e.indexOf(t)>=0:t===e}e.__esModule=!0
|
function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return Array.isArray(e)?e.indexOf(t)>=0:t===e}e.__esModule=!0
|
||||||
var o=n(1073),a=r(o),s=n(1027),l=r(s),u=n(1028),c=r(u),d=n(1064),f=r(d),p=n(989),h=r(p),m=n(1125),v=r(m),g=n(5),y=r(g),_=n(176),b=r(_),x=n(1104),w=r(x),k=n(1205),C=r(k),j=n(1082),T=r(j),E=y["default"].PropTypes.oneOf(["click","hover","focus"]),S=(0,
|
var o=n(1073),a=r(o),s=n(1027),l=r(s),u=n(1028),c=r(u),d=n(1064),f=r(d),p=n(989),h=r(p),m=n(1125),v=r(m),g=n(5),y=r(g),_=n(177),b=r(_),x=n(1104),w=r(x),k=n(1205),C=r(k),j=n(1082),T=r(j),E=y["default"].PropTypes.oneOf(["click","hover","focus"]),S=(0,
|
||||||
h["default"])({},C["default"].propTypes,{trigger:y["default"].PropTypes.oneOfType([E,y["default"].PropTypes.arrayOf(E)]),delay:y["default"].PropTypes.number,delayShow:y["default"].PropTypes.number,delayHide:y["default"].PropTypes.number,
|
h["default"])({},C["default"].propTypes,{trigger:y["default"].PropTypes.oneOfType([E,y["default"].PropTypes.arrayOf(E)]),delay:y["default"].PropTypes.number,delayShow:y["default"].PropTypes.number,delayHide:y["default"].PropTypes.number,
|
||||||
defaultOverlayShown:y["default"].PropTypes.bool,overlay:y["default"].PropTypes.node.isRequired,onBlur:y["default"].PropTypes.func,onClick:y["default"].PropTypes.func,onFocus:y["default"].PropTypes.func,
|
defaultOverlayShown:y["default"].PropTypes.bool,overlay:y["default"].PropTypes.node.isRequired,onBlur:y["default"].PropTypes.func,onClick:y["default"].PropTypes.func,onFocus:y["default"].PropTypes.func,
|
||||||
onMouseOut:y["default"].PropTypes.func,onMouseOver:y["default"].PropTypes.func,target:y["default"].PropTypes.oneOf([null]),onHide:y["default"].PropTypes.oneOf([null]),show:y["default"].PropTypes.oneOf([null])
|
onMouseOut:y["default"].PropTypes.func,onMouseOver:y["default"].PropTypes.func,target:y["default"].PropTypes.oneOf([null]),onHide:y["default"].PropTypes.oneOf([null]),show:y["default"].PropTypes.oneOf([null])
|
||||||
|
@ -36,6 +36,7 @@ class FormAlert extends SilverStripeComponent {
|
|||||||
* @returns {string} can be the following values "success", "warning", "danger", "info"
|
* @returns {string} can be the following values "success", "warning", "danger", "info"
|
||||||
*/
|
*/
|
||||||
getMessageStyle() {
|
getMessageStyle() {
|
||||||
|
// See ValidationResult::TYPE_ constant definitions in PHP.
|
||||||
switch (this.props.type) {
|
switch (this.props.type) {
|
||||||
case 'good':
|
case 'good':
|
||||||
case 'success':
|
case 'success':
|
||||||
|
@ -103,8 +103,9 @@ class FormBuilder extends SilverStripeComponent {
|
|||||||
const dataWithAction = Object.assign({}, data, {
|
const dataWithAction = Object.assign({}, data, {
|
||||||
[action]: 1,
|
[action]: 1,
|
||||||
});
|
});
|
||||||
|
const requestedSchema = this.props.responseRequestedSchema.join();
|
||||||
const headers = {
|
const headers = {
|
||||||
'X-Formschema-Request': 'state,schema',
|
'X-Formschema-Request': requestedSchema,
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -383,6 +384,9 @@ const basePropTypes = {
|
|||||||
submitting: PropTypes.bool,
|
submitting: PropTypes.bool,
|
||||||
baseFormComponent: PropTypes.func.isRequired,
|
baseFormComponent: PropTypes.func.isRequired,
|
||||||
baseFieldComponent: PropTypes.func.isRequired,
|
baseFieldComponent: PropTypes.func.isRequired,
|
||||||
|
responseRequestedSchema: PropTypes.arrayOf(PropTypes.oneOf([
|
||||||
|
'schema', 'state', 'errors', 'auto',
|
||||||
|
])),
|
||||||
};
|
};
|
||||||
|
|
||||||
FormBuilder.propTypes = Object.assign({}, basePropTypes, {
|
FormBuilder.propTypes = Object.assign({}, basePropTypes, {
|
||||||
@ -390,5 +394,9 @@ FormBuilder.propTypes = Object.assign({}, basePropTypes, {
|
|||||||
schema: schemaPropType.isRequired,
|
schema: schemaPropType.isRequired,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
FormBuilder.defaultProps = {
|
||||||
|
responseRequestedSchema: ['auto'],
|
||||||
|
};
|
||||||
|
|
||||||
export { basePropTypes, schemaPropType };
|
export { basePropTypes, schemaPropType };
|
||||||
export default FormBuilder;
|
export default FormBuilder;
|
||||||
|
@ -38,6 +38,25 @@ If you want to load the schema from a server via XHR, use the
|
|||||||
* `touchOnChange` (bool): See [redux-form](http://redux-form.com/6.0.5/docs/api/ReduxForm.md/)
|
* `touchOnChange` (bool): See [redux-form](http://redux-form.com/6.0.5/docs/api/ReduxForm.md/)
|
||||||
* `persistentSubmitErrors` (bool): See [redux-form](http://redux-form.com/6.0.5/docs/api/ReduxForm.md/)
|
* `persistentSubmitErrors` (bool): See [redux-form](http://redux-form.com/6.0.5/docs/api/ReduxForm.md/)
|
||||||
* `validate` (function): See [redux-form](http://redux-form.com/6.0.5/docs/api/ReduxForm.md/)
|
* `validate` (function): See [redux-form](http://redux-form.com/6.0.5/docs/api/ReduxForm.md/)
|
||||||
|
* `responseRequestedSchema` (array): This allows you to customise the response requested from the server
|
||||||
|
on submit. See below on "Handling submissions".
|
||||||
|
|
||||||
|
## Handling submissions
|
||||||
|
|
||||||
|
The `responseRequestedSchema` property will control the value of the 'X-Formschema-Request' header, which
|
||||||
|
in turn communicates to PHP the kind of response react would like. Your form should only specify the
|
||||||
|
bare minimum that it requires, as each header will represent additional overhead on all XHR requests.
|
||||||
|
|
||||||
|
This is an array which may be any combination of the below values:
|
||||||
|
|
||||||
|
* `schema`: The schema is requested on submit
|
||||||
|
* `state`: The state is requested on submit. Note that this may also include form errors.
|
||||||
|
* `errors`: The list of validation errors is returned in case of error.
|
||||||
|
* `auto`: (default) Conditionally return `errors` if there are errors, or `state` if there are none.
|
||||||
|
|
||||||
|
Note that these are only the requested header values; The PHP submission method may choose to ignore
|
||||||
|
these values, and return any combination of the above. Typically the only time this requested value
|
||||||
|
is respected is when handled by the default validation error handler (LeftAndMain::getSchemaResponse)
|
||||||
|
|
||||||
## Schema Structure
|
## Schema Structure
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import React, { PropTypes, Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import fetch from 'isomorphic-fetch';
|
import fetch from 'isomorphic-fetch';
|
||||||
|
import deepFreeze from 'deep-freeze-strict';
|
||||||
import {
|
import {
|
||||||
Field as ReduxFormField,
|
Field as ReduxFormField,
|
||||||
reduxForm,
|
reduxForm,
|
||||||
@ -20,6 +21,7 @@ class FormBuilderLoader extends Component {
|
|||||||
|
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
this.clearSchema = this.clearSchema.bind(this);
|
this.clearSchema = this.clearSchema.bind(this);
|
||||||
|
this.reduceSchemaErrors = this.reduceSchemaErrors.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -90,10 +92,13 @@ class FormBuilderLoader extends Component {
|
|||||||
|
|
||||||
return promise
|
return promise
|
||||||
.then(formSchema => {
|
.then(formSchema => {
|
||||||
if (formSchema) {
|
let schema = formSchema;
|
||||||
this.props.schemaActions.setSchema(this.props.schemaUrl, formSchema);
|
if (schema) {
|
||||||
|
// Strip errors out of schema response in preparation for setSchema and SubmissionError
|
||||||
|
schema = this.reduceSchemaErrors(schema);
|
||||||
|
this.props.schemaActions.setSchema(this.props.schemaUrl, schema);
|
||||||
}
|
}
|
||||||
return formSchema;
|
return schema;
|
||||||
})
|
})
|
||||||
// TODO Suggest storing messages in a separate redux store rather than throw an error
|
// TODO Suggest storing messages in a separate redux store rather than throw an error
|
||||||
// ref: https://github.com/erikras/redux-form/issues/94#issuecomment-143398399
|
// ref: https://github.com/erikras/redux-form/issues/94#issuecomment-143398399
|
||||||
@ -110,27 +115,40 @@ class FormBuilderLoader extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
overrideStateData(state) {
|
/**
|
||||||
if (!this.props.stateOverrides || !state) {
|
* Given a submitted schema, ensure that any errors property is merged safely into
|
||||||
return state;
|
* the state.
|
||||||
|
*
|
||||||
|
* @param {Object} schema - New schema result
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
reduceSchemaErrors(schema) {
|
||||||
|
// Skip if there are no errors
|
||||||
|
if (!schema.errors) {
|
||||||
|
return schema;
|
||||||
}
|
}
|
||||||
const fieldOverrides = this.props.stateOverrides.fields;
|
|
||||||
let fields = state.fields;
|
// Inherit state from current schema if not being assigned in this request
|
||||||
if (fieldOverrides && fields) {
|
let reduced = Object.assign({}, schema);
|
||||||
fields = fields.map((field) => {
|
if (!reduced.state) {
|
||||||
const fieldOverride = fieldOverrides.find((override) => override.name === field.name);
|
reduced = Object.assign({}, reduced, { state: this.props.schema.state });
|
||||||
if (!fieldOverride) {
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
// need to be recursive for the unknown-sized "data" properly
|
|
||||||
return merge.recursive(true, field, fieldOverride);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return Object.assign({},
|
|
||||||
state,
|
// Modify state.fields and replace state.messages
|
||||||
this.props.stateOverrides,
|
reduced = Object.assign({}, reduced, {
|
||||||
{ fields }
|
state: Object.assign({}, reduced.state, {
|
||||||
);
|
// Replace message property for each field
|
||||||
|
fields: reduced.state.fields.map((field) => Object.assign({}, field, {
|
||||||
|
message: schema.errors.find((error) => error.field === field.name),
|
||||||
|
})),
|
||||||
|
// Non-field messages
|
||||||
|
messages: schema.errors.filter((error) => !error.field),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Can be safely discarded
|
||||||
|
delete reduced.errors;
|
||||||
|
return deepFreeze(reduced);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,6 +191,7 @@ class FormBuilderLoader extends Component {
|
|||||||
* @return {Object} Promise from the AJAX request.
|
* @return {Object} Promise from the AJAX request.
|
||||||
*/
|
*/
|
||||||
fetch(schema = true, state = true) {
|
fetch(schema = true, state = true) {
|
||||||
|
// Note: `errors` is only valid for submissions, not schema requests, so omitted here
|
||||||
const headerValues = [];
|
const headerValues = [];
|
||||||
|
|
||||||
if (schema) {
|
if (schema) {
|
||||||
|
@ -8,11 +8,7 @@ export default function schemaReducer(state = initialState, action = null) {
|
|||||||
|
|
||||||
case ACTION_TYPES.SET_SCHEMA: {
|
case ACTION_TYPES.SET_SCHEMA: {
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
[action.payload.id]: Object.assign({}, state[action.payload.id], {
|
[action.payload.id]: Object.assign({}, state[action.payload.id], action.payload),
|
||||||
id: action.payload.id,
|
|
||||||
schema: action.payload.schema,
|
|
||||||
state: action.payload.state,
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,9 @@ use SilverStripe\Forms\HiddenField;
|
|||||||
use SilverStripe\Forms\FormAction;
|
use SilverStripe\Forms\FormAction;
|
||||||
use SilverStripe\Forms\FieldList;
|
use SilverStripe\Forms\FieldList;
|
||||||
use SilverStripe\Forms\Form;
|
use SilverStripe\Forms\Form;
|
||||||
|
use SilverStripe\Forms\RequiredFields;
|
||||||
use SilverStripe\ORM\SS_List;
|
use SilverStripe\ORM\SS_List;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\ORM\Versioning\ChangeSet;
|
use SilverStripe\ORM\Versioning\ChangeSet;
|
||||||
use SilverStripe\ORM\Versioning\ChangeSetItem;
|
use SilverStripe\ORM\Versioning\ChangeSetItem;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
@ -437,19 +439,22 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
|
|||||||
->setIcon('save'),
|
->setIcon('save'),
|
||||||
FormAction::create('cancel', _t('LeftAndMain.CANCEL', 'Cancel'))
|
FormAction::create('cancel', _t('LeftAndMain.CANCEL', 'Cancel'))
|
||||||
->setUseButtonTag(true)
|
->setUseButtonTag(true)
|
||||||
)
|
),
|
||||||
|
new RequiredFields('Name')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load into form
|
// Load into form
|
||||||
if($id && $record) {
|
if($id && $record) {
|
||||||
$form->loadDataFrom($record);
|
$form->loadDataFrom($record);
|
||||||
}
|
}
|
||||||
$form->getValidator()->addRequiredField('Name');
|
|
||||||
// Configure form to respond to validation errors with form schema
|
// Configure form to respond to validation errors with form schema
|
||||||
// if requested via react.
|
// if requested via react.
|
||||||
$form->setValidationResponseCallback(function() use ($form, $record) {
|
$form->setValidationResponseCallback(function(ValidationResult $errors) use ($form, $record) {
|
||||||
$schemaId = Controller::join_links($this->Link('schema/DetailEditForm'), $record->exists() ? $record->ID : '');
|
$schemaId = Controller::join_links(
|
||||||
return $this->getSchemaResponse($form, $schemaId);
|
$this->Link('schema/DetailEditForm'),
|
||||||
|
$record->isInDB() ? $record->ID : ''
|
||||||
|
);
|
||||||
|
return $this->getSchemaResponse($schemaId, $form, $errors);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
|
@ -35,6 +35,7 @@ use SilverStripe\i18n\i18n;
|
|||||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||||
use SilverStripe\ORM\Hierarchy\Hierarchy;
|
use SilverStripe\ORM\Hierarchy\Hierarchy;
|
||||||
use SilverStripe\ORM\SS_List;
|
use SilverStripe\ORM\SS_List;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\ORM\Versioning\Versioned;
|
use SilverStripe\ORM\Versioning\Versioned;
|
||||||
use SilverStripe\ORM\DataModel;
|
use SilverStripe\ORM\DataModel;
|
||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationException;
|
||||||
@ -55,18 +56,20 @@ use InvalidArgumentException;
|
|||||||
|
|
||||||
use SilverStripe\SiteConfig\SiteConfig;
|
use SilverStripe\SiteConfig\SiteConfig;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LeftAndMain is the parent class of all the two-pane views in the CMS.
|
* LeftAndMain is the parent class of all the two-pane views in the CMS.
|
||||||
* If you are wanting to add more areas to the CMS, you can do it by subclassing LeftAndMain.
|
* If you are wanting to add more areas to the CMS, you can do it by subclassing LeftAndMain.
|
||||||
*
|
*
|
||||||
* This is essentially an abstract class which should be subclassed.
|
* This is essentially an abstract class which should be subclassed.
|
||||||
* See {@link CMSMain} for a good example.
|
* See {@link CMSMain} for a good example.
|
||||||
*
|
|
||||||
* @property FormSchema $schema
|
|
||||||
*/
|
*/
|
||||||
class LeftAndMain extends Controller implements PermissionProvider {
|
class LeftAndMain extends Controller implements PermissionProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form schema header identifier
|
||||||
|
*/
|
||||||
|
const SCHEMA_HEADER = 'X-Formschema-Request';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable front-end debugging (increases verbosity) in dev mode.
|
* Enable front-end debugging (increases verbosity) in dev mode.
|
||||||
* Will be ignored in live environments.
|
* Will be ignored in live environments.
|
||||||
@ -156,9 +159,16 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
];
|
];
|
||||||
|
|
||||||
private static $dependencies = [
|
private static $dependencies = [
|
||||||
'schema' => '%$FormSchema'
|
'FormSchema' => '%$FormSchema'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current form schema helper
|
||||||
|
*
|
||||||
|
* @var FormSchema
|
||||||
|
*/
|
||||||
|
protected $schema = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign themes to use for cms
|
* Assign themes to use for cms
|
||||||
*
|
*
|
||||||
@ -296,6 +306,26 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get form schema helper
|
||||||
|
*
|
||||||
|
* @return FormSchema
|
||||||
|
*/
|
||||||
|
public function getFormSchema() {
|
||||||
|
return $this->schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set form schema helper for this controller
|
||||||
|
*
|
||||||
|
* @param FormSchema $schema
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setFormSchema(FormSchema $schema) {
|
||||||
|
$this->schema = $schema;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a JSON schema representing the current edit form.
|
* Gets a JSON schema representing the current edit form.
|
||||||
*
|
*
|
||||||
@ -305,7 +335,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
* @return HTTPResponse
|
* @return HTTPResponse
|
||||||
*/
|
*/
|
||||||
public function schema($request) {
|
public function schema($request) {
|
||||||
$response = $this->getResponse();
|
|
||||||
$formName = $request->param('FormName');
|
$formName = $request->param('FormName');
|
||||||
$itemID = $request->param('ItemID');
|
$itemID = $request->param('ItemID');
|
||||||
|
|
||||||
@ -322,72 +351,43 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$form = $this->{"get{$formName}"}($itemID);
|
$form = $this->{"get{$formName}"}($itemID);
|
||||||
|
$schemaID = $request->getURL();
|
||||||
$response->addHeader('Content-Type', 'application/json');
|
return $this->getSchemaResponse($schemaID, $form);
|
||||||
$response->setBody(Convert::raw2json($this->getSchemaForForm($form)));
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current request has a X-Formschema-Request header set.
|
||||||
|
* Used by conditional logic that responds to validation results
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function getSchemaRequested() {
|
||||||
|
$parts = $this->getRequest()->getHeader(static::SCHEMA_HEADER);
|
||||||
|
return !empty($parts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a form, generate a response containing the requested form
|
* Generate schema for the given form based on the X-Formschema-Request header value
|
||||||
* schema if X-Formschema-Request header is set.
|
|
||||||
*
|
*
|
||||||
* @param Form $form
|
* @param string $schemaID ID for this schema. Required.
|
||||||
* @param String $id Optional, will default to the current request URL
|
* @param Form $form Required for 'state' or 'schema' response
|
||||||
|
* @param ValidationResult $errors Required for 'error' response
|
||||||
|
* @param array $extraData Any extra data to be merged with the schema response
|
||||||
* @return HTTPResponse
|
* @return HTTPResponse
|
||||||
*/
|
*/
|
||||||
protected function getSchemaResponse($form, $id = null) {
|
protected function getSchemaResponse($schemaID, $form = null, ValidationResult $errors = null, $extraData = []) {
|
||||||
$request = $this->getRequest();
|
$parts = $this->getRequest()->getHeader(static::SCHEMA_HEADER);
|
||||||
if($request->getHeader('X-Formschema-Request')) {
|
$data = $this
|
||||||
$data = $this->getSchemaForForm($form, $id);
|
->getFormSchema()
|
||||||
$response = new HTTPResponse(Convert::raw2json($data));
|
->getMultipartSchema($parts, $schemaID, $form, $errors);
|
||||||
$response->addHeader('Content-Type', 'application/json');
|
|
||||||
|
|
||||||
// Clear non-schema form validation / data / message
|
if ($extraData) {
|
||||||
// since it does not need to be redirected
|
$data = array_merge($data, $extraData);
|
||||||
$form->clearMessage();
|
}
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$response = new HTTPResponse(Convert::raw2json($data));
|
||||||
* Returns a representation of the provided {@link Form} as structured data,
|
$response->addHeader('Content-Type', 'application/json');
|
||||||
* based on the request data.
|
return $response;
|
||||||
*
|
|
||||||
* @param Form $form
|
|
||||||
* @param String $id Optional, will default to the current request URL
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getSchemaForForm(Form $form, $id = null) {
|
|
||||||
$request = $this->getRequest();
|
|
||||||
$id = $id ? $id : $request->getURL();
|
|
||||||
$return = null;
|
|
||||||
|
|
||||||
// Valid values for the "X-Formschema-Request" header are "schema" and "state".
|
|
||||||
// If either of these values are set they will be stored in the $schemaParst array
|
|
||||||
// and used to construct the response body.
|
|
||||||
if ($schemaHeader = $request->getHeader('X-Formschema-Request')) {
|
|
||||||
$schemaParts = array_filter(explode(',', $schemaHeader), function($value) {
|
|
||||||
$validHeaderValues = ['schema', 'state'];
|
|
||||||
return in_array(trim($value), $validHeaderValues);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$schemaParts = ['schema'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$return = ['id' => $id];
|
|
||||||
|
|
||||||
if (in_array('schema', $schemaParts)) {
|
|
||||||
$return['schema'] = $this->schema->getSchema($form);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array('state', $schemaParts)) {
|
|
||||||
$return['state'] = $this->schema->getState($form);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1304,14 +1304,12 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
$this->setCurrentPageID($record->ID);
|
$this->setCurrentPageID($record->ID);
|
||||||
|
|
||||||
$message = _t('LeftAndMain.SAVEDUP', 'Saved.');
|
$message = _t('LeftAndMain.SAVEDUP', 'Saved.');
|
||||||
if($request->getHeader('X-Formschema-Request')) {
|
if($this->getSchemaRequested()) {
|
||||||
$schemaId = Controller::join_links($this->Link('schema/DetailEditForm'), $id);
|
$schemaId = Controller::join_links($this->Link('schema/DetailEditForm'), $id);
|
||||||
// Ensure that newly created records have all their data loaded back into the form.
|
// Ensure that newly created records have all their data loaded back into the form.
|
||||||
$form->loadDataFrom($record);
|
$form->loadDataFrom($record);
|
||||||
$form->setMessage($message, 'good');
|
$form->setMessage($message, 'good');
|
||||||
$data = $this->getSchemaForForm($form, $schemaId);
|
$response = $this->getSchemaResponse($schemaId, $form);
|
||||||
$response = new HTTPResponse(Convert::raw2json($data));
|
|
||||||
$response->addHeader('Content-Type', 'application/json');
|
|
||||||
} else {
|
} else {
|
||||||
$response = $this->getResponseNegotiator()->respond($request);
|
$response = $this->getResponseNegotiator()->respond($request);
|
||||||
}
|
}
|
||||||
@ -1580,10 +1578,9 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
$form->loadDataFrom($record);
|
$form->loadDataFrom($record);
|
||||||
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
||||||
$form->setAttribute('data-pjax-fragment', 'CurrentForm');
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm');
|
||||||
$form->setValidationResponseCallback(function() use ($negotiator, $form) {
|
$form->setValidationResponseCallback(function(ValidationResult $errors) use ($negotiator, $form) {
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
if($request->isAjax() && $negotiator) {
|
if($request->isAjax() && $negotiator) {
|
||||||
$form->setupFormErrors();
|
|
||||||
$result = $form->forTemplate();
|
$result = $form->forTemplate();
|
||||||
|
|
||||||
return $negotiator->respond($request, array(
|
return $negotiator->respond($request, array(
|
||||||
|
@ -65,7 +65,8 @@ the `setValue` method.
|
|||||||
|
|
||||||
:::php
|
:::php
|
||||||
public function validate($validator) {
|
public function validate($validator) {
|
||||||
if($this->value == 10) {
|
if($this->Value() == 10) {
|
||||||
|
$validator->validationError($this->Name(), 'This value cannot be 10');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ the `setValue` method.
|
|||||||
}
|
}
|
||||||
|
|
||||||
The `validate` method should return `true` if the value passes any validation and `false` if SilverStripe should trigger
|
The `validate` method should return `true` if the value passes any validation and `false` if SilverStripe should trigger
|
||||||
a validation error on the page.
|
a validation error on the page. In addition a useful error message must be set on the given validator.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
<div class="notice" markdown="1">
|
||||||
You can also override the entire `Form` validation by subclassing `Form` and defining a `validate` method on the form.
|
You can also override the entire `Form` validation by subclassing `Form` and defining a `validate` method on the form.
|
||||||
@ -141,7 +142,7 @@ reusable and would not be possible within the `CMS` or other automated `UI` but
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function doSubmitForm($data, $form) {
|
public function doSubmitForm($data, $form) {
|
||||||
// At this point, RequiredFields->validate() will have been called already,
|
// At this point, RequiredFields->isValid() will have been called already,
|
||||||
// so we can assume that the values exist. Say we want to make sure that email hasn't already been used.
|
// so we can assume that the values exist. Say we want to make sure that email hasn't already been used.
|
||||||
|
|
||||||
$check = Member::get()->filter('Email', $data['Email'])->first();
|
$check = Member::get()->filter('Email', $data['Email'])->first();
|
||||||
@ -214,7 +215,36 @@ classes added to each input. For Parsley we can structure the form like.
|
|||||||
|
|
||||||
An alternative (or additional) approach to validation is to place it directly on the database model. SilverStripe
|
An alternative (or additional) approach to validation is to place it directly on the database model. SilverStripe
|
||||||
provides a [api:DataObject::validate()] method to validate data at the model level. See
|
provides a [api:DataObject::validate()] method to validate data at the model level. See
|
||||||
[Data Model Validation](../model/validation).
|
[Data Model Validation](../model/validation).
|
||||||
|
|
||||||
|
## Form action validation
|
||||||
|
|
||||||
|
At times it's not possible for all validation or recoverable errors to be pre-determined in advance of form
|
||||||
|
submission, such as those generated by the form [api:Validator] object. Sometimes errors may occur within form
|
||||||
|
action methods, and it is necessary to display errors on the form after initial validation has been performed.
|
||||||
|
|
||||||
|
In this case you may throw a [api:ValidationException] object within your handler, optionally passing it an
|
||||||
|
error message, or a [api:ValidationResult] object containing the list of errors you wish to display.
|
||||||
|
|
||||||
|
E.g.
|
||||||
|
|
||||||
|
|
||||||
|
:::php
|
||||||
|
class MyController extends Controller
|
||||||
|
{
|
||||||
|
public function doSave($data, $form) {
|
||||||
|
$success = $this->sendEmail($data);
|
||||||
|
|
||||||
|
// Example error handling
|
||||||
|
if (!$success) {
|
||||||
|
throw new ValidationException('Sorry, we could not email to that address');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If success
|
||||||
|
return $this->redirect($this->Link('success'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
### Validation in the CMS
|
### Validation in the CMS
|
||||||
|
|
||||||
|
@ -1095,6 +1095,50 @@ Some methods on `Requirements` have had their method signatures changed:
|
|||||||
A new config `Requirements_Backend.combine_in_dev` has been added in order to allow combined files to be
|
A new config `Requirements_Backend.combine_in_dev` has been added in order to allow combined files to be
|
||||||
forced on during development. If this is off, combined files is only enabled in live environments.
|
forced on during development. If this is off, combined files is only enabled in live environments.
|
||||||
|
|
||||||
|
Form validation has been refactored significantly. A new `FormMessage` trait has been created to
|
||||||
|
handle field-level and form-level messages. This has the following properties:
|
||||||
|
|
||||||
|
* `setMessage` to assign a message, type, and cast
|
||||||
|
* `getMessage` retrieves the message string
|
||||||
|
* `getMessageType` retrieves the message type (E.g. error, good, info)
|
||||||
|
* `getMessageCast` retrieves the cast type
|
||||||
|
* `getMessageCastingHelper` retrieves the DBField cast to use for the appropriate message cast
|
||||||
|
* `getSchemaMessage` encodes this message for form schema use in ReactJS.
|
||||||
|
|
||||||
|
`Form` methods have been changed:
|
||||||
|
|
||||||
|
* `validate` is replaced with `validationResult` instead, which returns a `ValidationResult` instance.
|
||||||
|
This is no longer automatically persisted in the state by default, unless a redirection occurs.
|
||||||
|
You can also save any response in the state by manually invoking `saveFormState` inside a custom
|
||||||
|
validation response handler.
|
||||||
|
* `setupFormErrors` renamed to `restoreFormState`
|
||||||
|
* `resetValidation` renamed to `clearFormState`
|
||||||
|
* `loadMessagesFrom` method created to load a ValidationResult into a form.
|
||||||
|
* `setMessage`. third parameter is now $cast type
|
||||||
|
* `messageForForm` removed. Use `setMessage` or `sessionMessage` instead.
|
||||||
|
* `getSessionValidationResult` / `setSessionValidationResult` used to get / set session errors
|
||||||
|
* `getSessionData` / `setSessionData` used to get / set field values cached in the session
|
||||||
|
* `getAjaxErrorResponse` and `getRedirectReferer` created to simplify `getValidationErrorResponse`
|
||||||
|
* `addErrorMessage` removed. Users can either use `sessionMessage` or `sessionError` to add a
|
||||||
|
form level message, throw a ValidationException during submission, or add a custom validator.
|
||||||
|
|
||||||
|
`Validator` methods have changed:
|
||||||
|
|
||||||
|
* `validate` method now returns a `ValidationResult` instance.
|
||||||
|
* `requireField` method removed. Use `RequiredFields` subclass instead.
|
||||||
|
|
||||||
|
`ValidationResult` now has these methods:
|
||||||
|
|
||||||
|
* `serialize` / `unserialize` for saving within session state
|
||||||
|
* `messageList` renamed to `getMessages`
|
||||||
|
* `error` method replaced with `addMessage` / `addError` / `addFieldMessage` / `addFieldError`
|
||||||
|
* `valid` renamed to `isValid`
|
||||||
|
|
||||||
|
`ValidationException` has these changes:
|
||||||
|
|
||||||
|
* `$message` second constructor parameter is removed. Constructor only accepts `$result`,
|
||||||
|
which may be a string, and optional `$code`
|
||||||
|
|
||||||
#### <a name="overview-template-removed"></a>Template and Form Removed API
|
#### <a name="overview-template-removed"></a>Template and Form Removed API
|
||||||
|
|
||||||
* Removed `TabularStyle`
|
* Removed `TabularStyle`
|
||||||
@ -1103,12 +1147,24 @@ forced on during development. If this is off, combined files is only enabled in
|
|||||||
* `getTabPathRewrites`
|
* `getTabPathRewrites`
|
||||||
* `setTabPathRewrites`
|
* `setTabPathRewrites`
|
||||||
* `rewriteTabPath`
|
* `rewriteTabPath`
|
||||||
* Removed `Form` methods:
|
* Removed `Form` methods (see above for replacements):
|
||||||
* `transformTo`
|
* `transformTo`
|
||||||
* `callfieldmethod`
|
* `callfieldmethod`
|
||||||
* `single_field_required`
|
* `single_field_required`
|
||||||
* `current_action`
|
* `current_action`
|
||||||
* `set_current_action`
|
* `set_current_action`
|
||||||
|
* `setupFormErrors`
|
||||||
|
* `resetValidation`
|
||||||
|
* `messageForForm`
|
||||||
|
* `addErrorMessage`
|
||||||
|
* Removed `Validator::requireField()` method.
|
||||||
|
* Removed `ValidationResult` (see above for replacements):
|
||||||
|
* `messageList`
|
||||||
|
* `codeList`
|
||||||
|
* `message`
|
||||||
|
* `starredList`
|
||||||
|
* `error`
|
||||||
|
* `valid`
|
||||||
* Removed `ReportAdminForm.ss` template
|
* Removed `ReportAdminForm.ss` template
|
||||||
* `FormField::dontEscape()` has been removed. Escaping is now managed on a class by class basis.
|
* `FormField::dontEscape()` has been removed. Escaping is now managed on a class by class basis.
|
||||||
* Removed `PermissionCheckboxSetField::getAssignedPermissionCodes()` (never implemented)
|
* Removed `PermissionCheckboxSetField::getAssignedPermissionCodes()` (never implemented)
|
||||||
|
@ -84,9 +84,9 @@ Each release is labeled in the format `$MAJOR`.`$MINOR`.`$PATCH`. For example, 3
|
|||||||
|
|
||||||
* `$MAJOR` version is incremented if any backwards incompatible changes are introduced to the public API.
|
* `$MAJOR` version is incremented if any backwards incompatible changes are introduced to the public API.
|
||||||
* `$MINOR` version is incremented if new, backwards compatible **functionality** is introduced to the public API or
|
* `$MINOR` version is incremented if new, backwards compatible **functionality** is introduced to the public API or
|
||||||
improvements are introduced within the private code.
|
improvements are introduced within the private code.
|
||||||
* `$PATCH` version is incremented if only backwards compatible **bug fixes** are introduced. A bug fix is defined as
|
* `$PATCH` version is incremented if only backwards compatible **bug fixes** are introduced. A bug fix is defined as
|
||||||
an internal change that fixes incorrect behavior.
|
an internal change that fixes incorrect behavior.
|
||||||
|
|
||||||
**Public API** refers to any aspect of the system that has been designed to be used by SilverStripe modules & site developers. In SilverStripe 3, because we haven't been clear, in principle we have to treat every public or protected method as *potentially* part of the public API, but sometimes it comes to a judgement call about how likely it is that a given method will have been used in a particular way. If we were strict about never changing publicly exposed behaviour, it would be difficult to fix any bug whatsoever, which isn't in the interests of our user community.
|
**Public API** refers to any aspect of the system that has been designed to be used by SilverStripe modules & site developers. In SilverStripe 3, because we haven't been clear, in principle we have to treat every public or protected method as *potentially* part of the public API, but sometimes it comes to a judgement call about how likely it is that a given method will have been used in a particular way. If we were strict about never changing publicly exposed behaviour, it would be difficult to fix any bug whatsoever, which isn't in the interests of our user community.
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ use SilverStripe\ORM\ValidationResult;
|
|||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationException;
|
||||||
use SilverStripe\ORM\FieldType\DBComposite;
|
use SilverStripe\ORM\FieldType\DBComposite;
|
||||||
use SilverStripe\Security\Permission;
|
use SilverStripe\Security\Permission;
|
||||||
use SilverStripe\Core\Convert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a file reference stored in a database
|
* Represents a file reference stored in a database
|
||||||
@ -24,552 +23,541 @@ use SilverStripe\Core\Convert;
|
|||||||
class DBFile extends DBComposite implements AssetContainer, Thumbnail
|
class DBFile extends DBComposite implements AssetContainer, Thumbnail
|
||||||
{
|
{
|
||||||
|
|
||||||
use ImageManipulation;
|
use ImageManipulation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of allowed file categories.
|
* List of allowed file categories.
|
||||||
*
|
*
|
||||||
* {@see File::$app_categories}
|
* {@see File::$app_categories}
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $allowedCategories = array();
|
protected $allowedCategories = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of image mime types supported by the image manipulations API
|
* List of image mime types supported by the image manipulations API
|
||||||
*
|
*
|
||||||
* {@see File::app_categories} for matching extensions.
|
* {@see File::app_categories} for matching extensions.
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $supported_images = array(
|
private static $supported_images = array(
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/gif',
|
'image/gif',
|
||||||
'image/png'
|
'image/png'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new image manipulation
|
* Create a new image manipulation
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param array|string $allowed List of allowed file categories (not extensions), as per File::$app_categories
|
* @param array|string $allowed List of allowed file categories (not extensions), as per File::$app_categories
|
||||||
*/
|
*/
|
||||||
public function __construct($name = null, $allowed = array())
|
public function __construct($name = null, $allowed = array())
|
||||||
{
|
{
|
||||||
parent::__construct($name);
|
parent::__construct($name);
|
||||||
$this->setAllowedCategories($allowed);
|
$this->setAllowedCategories($allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a valid non-empty image exists behind this asset, which is a format
|
* Determine if a valid non-empty image exists behind this asset, which is a format
|
||||||
* compatible with image manipulations
|
* compatible with image manipulations
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function getIsImage()
|
public function getIsImage()
|
||||||
{
|
{
|
||||||
// Check file type
|
// Check file type
|
||||||
$mime = $this->getMimeType();
|
$mime = $this->getMimeType();
|
||||||
return $mime && in_array($mime, $this->config()->supported_images);
|
return $mime && in_array($mime, $this->config()->supported_images);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return AssetStore
|
* @return AssetStore
|
||||||
*/
|
*/
|
||||||
protected function getStore()
|
protected function getStore()
|
||||||
{
|
{
|
||||||
return Injector::inst()->get('AssetStore');
|
return Injector::inst()->get('AssetStore');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static $composite_db = array(
|
private static $composite_db = array(
|
||||||
"Hash" => "Varchar(255)", // SHA of the base content
|
"Hash" => "Varchar(255)", // SHA of the base content
|
||||||
"Filename" => "Varchar(255)", // Path identifier of the base content
|
"Filename" => "Varchar(255)", // Path identifier of the base content
|
||||||
"Variant" => "Varchar(255)", // Identifier of the variant to the base, if given
|
"Variant" => "Varchar(255)", // Identifier of the variant to the base, if given
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $casting = array(
|
private static $casting = array(
|
||||||
'URL' => 'Varchar',
|
'URL' => 'Varchar',
|
||||||
'AbsoluteURL' => 'Varchar',
|
'AbsoluteURL' => 'Varchar',
|
||||||
'Basename' => 'Varchar',
|
'Basename' => 'Varchar',
|
||||||
'Title' => 'Varchar',
|
'Title' => 'Varchar',
|
||||||
'MimeType' => 'Varchar',
|
'MimeType' => 'Varchar',
|
||||||
'String' => 'Text',
|
'String' => 'Text',
|
||||||
'Tag' => 'HTMLFragment',
|
'Tag' => 'HTMLFragment',
|
||||||
'Size' => 'Varchar'
|
'Size' => 'Varchar'
|
||||||
);
|
);
|
||||||
|
|
||||||
public function scaffoldFormField($title = null, $params = null)
|
public function scaffoldFormField($title = null, $params = null)
|
||||||
{
|
{
|
||||||
return AssetField::create($this->getName(), $title);
|
return AssetField::create($this->getName(), $title);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a html5 tag of the appropriate for this file (normally img or a)
|
* Return a html5 tag of the appropriate for this file (normally img or a)
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function XML()
|
public function XML()
|
||||||
{
|
{
|
||||||
return $this->getTag() ?: '';
|
return $this->getTag() ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a html5 tag of the appropriate for this file (normally img or a)
|
* Return a html5 tag of the appropriate for this file (normally img or a)
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTag()
|
public function getTag()
|
||||||
{
|
{
|
||||||
$template = $this->getFrontendTemplate();
|
$template = $this->getFrontendTemplate();
|
||||||
if(empty($template)) {
|
if (empty($template)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return (string)$this->renderWith($template);
|
return (string)$this->renderWith($template);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the template to render as on the frontend
|
* Determine the template to render as on the frontend
|
||||||
*
|
*
|
||||||
* @return string Name of template
|
* @return string Name of template
|
||||||
*/
|
*/
|
||||||
public function getFrontendTemplate()
|
public function getFrontendTemplate()
|
||||||
{
|
{
|
||||||
// Check that path is available
|
// Check that path is available
|
||||||
$url = $this->getURL();
|
$url = $this->getURL();
|
||||||
if(empty($url)) {
|
if (empty($url)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image template for supported images
|
// Image template for supported images
|
||||||
if($this->getIsImage()) {
|
if ($this->getIsImage()) {
|
||||||
return 'DBFile_image';
|
return 'DBFile_image';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default download
|
// Default download
|
||||||
return 'DBFile_download';
|
return 'DBFile_download';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get trailing part of filename
|
* Get trailing part of filename
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getBasename()
|
public function getBasename()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return basename($this->getSourceURL());
|
return basename($this->getSourceURL());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get file extension
|
* Get file extension
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getExtension()
|
public function getExtension()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return pathinfo($this->Filename, PATHINFO_EXTENSION);
|
return pathinfo($this->Filename, PATHINFO_EXTENSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alt title for this
|
* Alt title for this
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTitle()
|
public function getTitle()
|
||||||
{
|
{
|
||||||
// If customised, use the customised title
|
// If customised, use the customised title
|
||||||
if($this->failover && ($title = $this->failover->Title)) {
|
if ($this->failover && ($title = $this->failover->Title)) {
|
||||||
return $title;
|
return $title;
|
||||||
}
|
}
|
||||||
// fallback to using base name
|
// fallback to using base name
|
||||||
return $this->getBasename();
|
return $this->getBasename();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array())
|
public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array())
|
||||||
{
|
{
|
||||||
$this->assertFilenameValid($filename ?: $path);
|
$this->assertFilenameValid($filename ?: $path);
|
||||||
$result = $this
|
$result = $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->setFromLocalFile($path, $filename, $hash, $variant, $config);
|
->setFromLocalFile($path, $filename, $hash, $variant, $config);
|
||||||
// Update from result
|
// Update from result
|
||||||
if($result) {
|
if ($result) {
|
||||||
$this->setValue($result);
|
$this->setValue($result);
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array())
|
public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array())
|
||||||
{
|
{
|
||||||
$this->assertFilenameValid($filename);
|
$this->assertFilenameValid($filename);
|
||||||
$result = $this
|
$result = $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->setFromStream($stream, $filename, $hash, $variant, $config);
|
->setFromStream($stream, $filename, $hash, $variant, $config);
|
||||||
// Update from result
|
// Update from result
|
||||||
if($result) {
|
if ($result) {
|
||||||
$this->setValue($result);
|
$this->setValue($result);
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFromString($data, $filename, $hash = null, $variant = null, $config = array())
|
public function setFromString($data, $filename, $hash = null, $variant = null, $config = array())
|
||||||
{
|
{
|
||||||
$this->assertFilenameValid($filename);
|
$this->assertFilenameValid($filename);
|
||||||
$result = $this
|
$result = $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->setFromString($data, $filename, $hash, $variant, $config);
|
->setFromString($data, $filename, $hash, $variant, $config);
|
||||||
// Update from result
|
// Update from result
|
||||||
if($result) {
|
if ($result) {
|
||||||
$this->setValue($result);
|
$this->setValue($result);
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStream()
|
public function getStream()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->getAsStream($this->Filename, $this->Hash, $this->Variant);
|
->getAsStream($this->Filename, $this->Hash, $this->Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getString()
|
public function getString()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->getAsString($this->Filename, $this->Hash, $this->Variant);
|
->getAsString($this->Filename, $this->Hash, $this->Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getURL($grant = true)
|
public function getURL($grant = true)
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$url = $this->getSourceURL($grant);
|
$url = $this->getSourceURL($grant);
|
||||||
$this->updateURL($url);
|
$this->updateURL($url);
|
||||||
$this->extend('updateURL', $url);
|
$this->extend('updateURL', $url);
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get URL, but without resampling.
|
* Get URL, but without resampling.
|
||||||
* Note that this will return the url even if the file does not exist.
|
* Note that this will return the url even if the file does not exist.
|
||||||
*
|
*
|
||||||
* @param bool $grant Ensures that the url for any protected assets is granted for the current user.
|
* @param bool $grant Ensures that the url for any protected assets is granted for the current user.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getSourceURL($grant = true)
|
public function getSourceURL($grant = true)
|
||||||
{
|
{
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->getAsURL($this->Filename, $this->Hash, $this->Variant, $grant);
|
->getAsURL($this->Filename, $this->Hash, $this->Variant, $grant);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the absolute URL to this resource
|
* Get the absolute URL to this resource
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getAbsoluteURL()
|
public function getAbsoluteURL()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return Director::absoluteURL($this->getURL());
|
return Director::absoluteURL($this->getURL());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMetaData()
|
public function getMetaData()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->getMetadata($this->Filename, $this->Hash, $this->Variant);
|
->getMetadata($this->Filename, $this->Hash, $this->Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMimeType()
|
public function getMimeType()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->getMimeType($this->Filename, $this->Hash, $this->Variant);
|
->getMimeType($this->Filename, $this->Hash, $this->Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getValue()
|
public function getValue()
|
||||||
{
|
{
|
||||||
if(!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return array(
|
return array(
|
||||||
'Filename' => $this->Filename,
|
'Filename' => $this->Filename,
|
||||||
'Hash' => $this->Hash,
|
'Hash' => $this->Hash,
|
||||||
'Variant' => $this->Variant
|
'Variant' => $this->Variant
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getVisibility()
|
public function getVisibility()
|
||||||
{
|
{
|
||||||
if(empty($this->Filename)) {
|
if (empty($this->Filename)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->getVisibility($this->Filename, $this->Hash);
|
->getVisibility($this->Filename, $this->Hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exists()
|
public function exists()
|
||||||
{
|
{
|
||||||
if(empty($this->Filename)) {
|
if (empty($this->Filename)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->exists($this->Filename, $this->Hash, $this->Variant);
|
->exists($this->Filename, $this->Hash, $this->Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFilename()
|
public function getFilename()
|
||||||
{
|
{
|
||||||
return $this->getField('Filename');
|
return $this->getField('Filename');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHash()
|
public function getHash()
|
||||||
{
|
{
|
||||||
return $this->getField('Hash');
|
return $this->getField('Hash');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getVariant()
|
public function getVariant()
|
||||||
{
|
{
|
||||||
return $this->getField('Variant');
|
return $this->getField('Variant');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return file size in bytes.
|
* Return file size in bytes.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getAbsoluteSize()
|
public function getAbsoluteSize()
|
||||||
{
|
{
|
||||||
$metadata = $this->getMetaData();
|
$metadata = $this->getMetaData();
|
||||||
if(isset($metadata['size'])) {
|
if (isset($metadata['size'])) {
|
||||||
return $metadata['size'];
|
return $metadata['size'];
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customise this object with an "original" record for getting other customised fields
|
* Customise this object with an "original" record for getting other customised fields
|
||||||
*
|
*
|
||||||
* @param AssetContainer $original
|
* @param AssetContainer $original
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setOriginal($original)
|
public function setOriginal($original)
|
||||||
{
|
{
|
||||||
$this->failover = $original;
|
$this->failover = $original;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of allowed file categories
|
* Get list of allowed file categories
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getAllowedCategories()
|
public function getAllowedCategories()
|
||||||
{
|
{
|
||||||
return $this->allowedCategories;
|
return $this->allowedCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign allowed categories
|
* Assign allowed categories
|
||||||
*
|
*
|
||||||
* @param array|string $categories
|
* @param array|string $categories
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setAllowedCategories($categories)
|
public function setAllowedCategories($categories)
|
||||||
{
|
{
|
||||||
if(is_string($categories)) {
|
if (is_string($categories)) {
|
||||||
$categories = preg_split('/\s*,\s*/', $categories);
|
$categories = preg_split('/\s*,\s*/', $categories);
|
||||||
}
|
}
|
||||||
$this->allowedCategories = (array)$categories;
|
$this->allowedCategories = (array)$categories;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the list of extensions (if limited) for this field. Empty list
|
* Gets the list of extensions (if limited) for this field. Empty list
|
||||||
* means there is no restriction on allowed types.
|
* means there is no restriction on allowed types.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getAllowedExtensions()
|
protected function getAllowedExtensions()
|
||||||
{
|
{
|
||||||
$categories = $this->getAllowedCategories();
|
$categories = $this->getAllowedCategories();
|
||||||
return File::get_category_extensions($categories);
|
return File::get_category_extensions($categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that this DBFile accepts this filename as valid
|
* Validate that this DBFile accepts this filename as valid
|
||||||
*
|
*
|
||||||
* @param string $filename
|
* @param string $filename
|
||||||
* @throws ValidationException
|
* @throws ValidationException
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function isValidFilename($filename)
|
protected function isValidFilename($filename)
|
||||||
{
|
{
|
||||||
$extension = strtolower(File::get_file_extension($filename));
|
$extension = strtolower(File::get_file_extension($filename));
|
||||||
|
|
||||||
// Validate true if within the list of allowed extensions
|
// Validate true if within the list of allowed extensions
|
||||||
$allowed = $this->getAllowedExtensions();
|
$allowed = $this->getAllowedExtensions();
|
||||||
if($allowed) {
|
if ($allowed) {
|
||||||
return in_array($extension, $allowed);
|
return in_array($extension, $allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no extensions are configured, fallback to global list
|
// If no extensions are configured, fallback to global list
|
||||||
$globalList = File::config()->allowed_extensions;
|
$globalList = File::config()->allowed_extensions;
|
||||||
if(in_array($extension, $globalList)) {
|
if (in_array($extension, $globalList)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only admins can bypass global rules
|
// Only admins can bypass global rules
|
||||||
return !File::config()->apply_restrictions_to_admin && Permission::check('ADMIN');
|
return !File::config()->apply_restrictions_to_admin && Permission::check('ADMIN');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check filename, and raise a ValidationException if invalid
|
* Check filename, and raise a ValidationException if invalid
|
||||||
*
|
*
|
||||||
* @param string $filename
|
* @param string $filename
|
||||||
* @throws ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
protected function assertFilenameValid($filename)
|
protected function assertFilenameValid($filename)
|
||||||
{
|
{
|
||||||
$result = new ValidationResult();
|
$result = new ValidationResult();
|
||||||
$this->validate($result, $filename);
|
$this->validate($result, $filename);
|
||||||
if(!$result->valid()) {
|
if (!$result->isValid()) {
|
||||||
throw new ValidationException($result);
|
throw new ValidationException($result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to validate this record against a validation result
|
* Hook to validate this record against a validation result
|
||||||
*
|
*
|
||||||
* @param ValidationResult $result
|
* @param ValidationResult $result
|
||||||
* @param string $filename Optional filename to validate. If omitted, the current value is validated.
|
* @param string $filename Optional filename to validate. If omitted, the current value is validated.
|
||||||
* @return bool Valid flag
|
* @return bool Valid flag
|
||||||
*/
|
*/
|
||||||
public function validate(ValidationResult $result, $filename = null)
|
public function validate(ValidationResult $result, $filename = null)
|
||||||
{
|
{
|
||||||
if(empty($filename)) {
|
if (empty($filename)) {
|
||||||
$filename = $this->getFilename();
|
$filename = $this->getFilename();
|
||||||
}
|
}
|
||||||
if(empty($filename) || $this->isValidFilename($filename)) {
|
if (empty($filename) || $this->isValidFilename($filename)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check allowed extensions
|
$message = _t('File.INVALIDEXTENSIONSHORT', 'Extension is not allowed');
|
||||||
$extensions = $this->getAllowedExtensions();
|
$result->addError($message);
|
||||||
if(empty($extensions)) {
|
return false;
|
||||||
$extensions = File::config()->allowed_extensions;
|
}
|
||||||
}
|
|
||||||
sort($extensions);
|
|
||||||
$message = _t(
|
|
||||||
'File.INVALIDEXTENSION',
|
|
||||||
'Extension is not allowed (valid: {extensions})',
|
|
||||||
'Argument 1: Comma-separated list of valid extensions',
|
|
||||||
array('extensions' => wordwrap(implode(', ',$extensions)))
|
|
||||||
);
|
|
||||||
$result->addError($message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setField($field, $value, $markChanged = true)
|
public function setField($field, $value, $markChanged = true)
|
||||||
{
|
{
|
||||||
// Catch filename validation on direct assignment
|
// Catch filename validation on direct assignment
|
||||||
if($field === 'Filename' && $value) {
|
if ($field === 'Filename' && $value) {
|
||||||
$this->assertFilenameValid($value);
|
$this->assertFilenameValid($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::setField($field, $value, $markChanged);
|
return parent::setField($field, $value, $markChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the file type in an appropriate format.
|
* Returns the size of the file type in an appropriate format.
|
||||||
*
|
*
|
||||||
* @return string|false String value, or false if doesn't exist
|
* @return string|false String value, or false if doesn't exist
|
||||||
*/
|
*/
|
||||||
public function getSize()
|
public function getSize()
|
||||||
{
|
{
|
||||||
$size = $this->getAbsoluteSize();
|
$size = $this->getAbsoluteSize();
|
||||||
if($size) {
|
if ($size) {
|
||||||
return File::format_size($size);
|
return File::format_size($size);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteFile()
|
public function deleteFile()
|
||||||
{
|
{
|
||||||
if(!$this->Filename) {
|
if (!$this->Filename) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->delete($this->Filename, $this->Hash);
|
->delete($this->Filename, $this->Hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function publishFile()
|
public function publishFile()
|
||||||
{
|
{
|
||||||
if($this->Filename) {
|
if ($this->Filename) {
|
||||||
$this
|
$this
|
||||||
->getStore()
|
->getStore()
|
||||||
->publish($this->Filename, $this->Hash);
|
->publish($this->Filename, $this->Hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function protectFile()
|
public function protectFile()
|
||||||
{
|
{
|
||||||
if($this->Filename) {
|
if ($this->Filename) {
|
||||||
$this
|
$this
|
||||||
->getStore()
|
->getStore()
|
||||||
->protect($this->Filename, $this->Hash);
|
->protect($this->Filename, $this->Hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function grantFile()
|
public function grantFile()
|
||||||
{
|
{
|
||||||
if($this->Filename) {
|
if ($this->Filename) {
|
||||||
$this
|
$this
|
||||||
->getStore()
|
->getStore()
|
||||||
->grant($this->Filename, $this->Hash);
|
->grant($this->Filename, $this->Hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revokeFile()
|
public function revokeFile()
|
||||||
{
|
{
|
||||||
if($this->Filename) {
|
if ($this->Filename) {
|
||||||
$this
|
$this
|
||||||
->getStore()
|
->getStore()
|
||||||
->revoke($this->Filename, $this->Hash);
|
->revoke($this->Filename, $this->Hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canViewFile()
|
public function canViewFile()
|
||||||
{
|
{
|
||||||
return $this->Filename
|
return $this->Filename
|
||||||
&& $this
|
&& $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->canView($this->Filename, $this->Hash);
|
->canView($this->Filename, $this->Hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,374 +33,372 @@ use SimpleXMLElement;
|
|||||||
*/
|
*/
|
||||||
class FunctionalTest extends SapphireTest
|
class FunctionalTest extends SapphireTest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Set this to true on your sub-class to disable the use of themes in this test.
|
* Set this to true on your sub-class to disable the use of themes in this test.
|
||||||
* This can be handy for functional testing of modules without having to worry about whether a user has changed
|
* This can be handy for functional testing of modules without having to worry about whether a user has changed
|
||||||
* behaviour by replacing the theme.
|
* behaviour by replacing the theme.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected static $disable_themes = false;
|
protected static $disable_themes = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set this to true on your sub-class to use the draft site by default for every test in this class.
|
* Set this to true on your sub-class to use the draft site by default for every test in this class.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected static $use_draft_site = false;
|
protected static $use_draft_site = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TestSession
|
* @var TestSession
|
||||||
*/
|
*/
|
||||||
protected $mainSession = null;
|
protected $mainSession = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CSSContentParser for the most recently requested page.
|
* CSSContentParser for the most recently requested page.
|
||||||
*
|
*
|
||||||
* @var CSSContentParser
|
* @var CSSContentParser
|
||||||
*/
|
*/
|
||||||
protected $cssParser = null;
|
protected $cssParser = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is true, then 30x Location headers will be automatically followed.
|
* If this is true, then 30x Location headers will be automatically followed.
|
||||||
* If not, then you will have to manaully call $this->mainSession->followRedirection() to follow them.
|
* If not, then you will have to manaully call $this->mainSession->followRedirection() to follow them.
|
||||||
* However, this will let you inspect the intermediary headers
|
* However, this will let you inspect the intermediary headers
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $autoFollowRedirection = true;
|
protected $autoFollowRedirection = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link Session} object for this test
|
* Returns the {@link Session} object for this test
|
||||||
*
|
*
|
||||||
* @return Session
|
* @return Session
|
||||||
*/
|
*/
|
||||||
public function session()
|
public function session()
|
||||||
{
|
{
|
||||||
return $this->mainSession->session();
|
return $this->mainSession->session();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
// Skip calling FunctionalTest directly.
|
// Skip calling FunctionalTest directly.
|
||||||
if(get_class($this) == __CLASS__) {
|
if (get_class($this) == __CLASS__) {
|
||||||
$this->markTestSkipped(sprintf('Skipping %s ', get_class($this)));
|
$this->markTestSkipped(sprintf('Skipping %s ', get_class($this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
$this->mainSession = new TestSession();
|
$this->mainSession = new TestSession();
|
||||||
|
|
||||||
// Disable theme, if necessary
|
// Disable theme, if necessary
|
||||||
if(static::get_disable_themes()) {
|
if (static::get_disable_themes()) {
|
||||||
SSViewer::config()->update('theme_enabled', false);
|
SSViewer::config()->update('theme_enabled', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to draft site, if necessary
|
// Switch to draft site, if necessary
|
||||||
if(static::get_use_draft_site()) {
|
if (static::get_use_draft_site()) {
|
||||||
$this->useDraftSite();
|
$this->useDraftSite();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unprotect the site, tests are running with the assumption it's off. They will enable it on a case-by-case
|
// Unprotect the site, tests are running with the assumption it's off. They will enable it on a case-by-case
|
||||||
// basis.
|
// basis.
|
||||||
BasicAuth::protect_entire_site(false);
|
BasicAuth::protect_entire_site(false);
|
||||||
|
|
||||||
SecurityToken::disable();
|
SecurityToken::disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
{
|
{
|
||||||
SecurityToken::enable();
|
SecurityToken::enable();
|
||||||
|
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
unset($this->mainSession);
|
unset($this->mainSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a test while mocking the base url with the provided value
|
* Run a test while mocking the base url with the provided value
|
||||||
* @param string $url The base URL to use for this test
|
* @param string $url The base URL to use for this test
|
||||||
* @param callable $callback The test to run
|
* @param callable $callback The test to run
|
||||||
*/
|
*/
|
||||||
protected function withBaseURL($url, $callback)
|
protected function withBaseURL($url, $callback)
|
||||||
{
|
{
|
||||||
$oldBase = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_url');
|
$oldBase = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_url');
|
||||||
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', $url);
|
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', $url);
|
||||||
$callback($this);
|
$callback($this);
|
||||||
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', $oldBase);
|
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', $oldBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a test while mocking the base folder with the provided value
|
* Run a test while mocking the base folder with the provided value
|
||||||
* @param string $folder The base folder to use for this test
|
* @param string $folder The base folder to use for this test
|
||||||
* @param callable $callback The test to run
|
* @param callable $callback The test to run
|
||||||
*/
|
*/
|
||||||
protected function withBaseFolder($folder, $callback)
|
protected function withBaseFolder($folder, $callback)
|
||||||
{
|
{
|
||||||
$oldFolder = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_folder');
|
$oldFolder = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_folder');
|
||||||
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_folder', $folder);
|
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_folder', $folder);
|
||||||
$callback($this);
|
$callback($this);
|
||||||
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_folder', $oldFolder);
|
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_folder', $oldFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit a get request
|
* Submit a get request
|
||||||
* @uses Director::test()
|
* @uses Director::test()
|
||||||
*
|
*
|
||||||
* @param string $url
|
* @param string $url
|
||||||
* @param Session $session
|
* @param Session $session
|
||||||
* @param array $headers
|
* @param array $headers
|
||||||
* @param array $cookies
|
* @param array $cookies
|
||||||
* @return HTTPResponse
|
* @return HTTPResponse
|
||||||
*/
|
*/
|
||||||
public function get($url, $session = null, $headers = null, $cookies = null)
|
public function get($url, $session = null, $headers = null, $cookies = null)
|
||||||
{
|
{
|
||||||
$this->cssParser = null;
|
$this->cssParser = null;
|
||||||
$response = $this->mainSession->get($url, $session, $headers, $cookies);
|
$response = $this->mainSession->get($url, $session, $headers, $cookies);
|
||||||
if($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) {
|
if ($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) {
|
||||||
$response = $this->mainSession->followRedirection();
|
$response = $this->mainSession->followRedirection();
|
||||||
}
|
}
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit a post request
|
* Submit a post request
|
||||||
*
|
*
|
||||||
* @uses Director::test()
|
* @uses Director::test()
|
||||||
* @param string $url
|
* @param string $url
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @param array $headers
|
* @param array $headers
|
||||||
* @param Session $session
|
* @param Session $session
|
||||||
* @param string $body
|
* @param string $body
|
||||||
* @param array $cookies
|
* @param array $cookies
|
||||||
* @return HTTPResponse
|
* @return HTTPResponse
|
||||||
*/
|
*/
|
||||||
public function post($url, $data, $headers = null, $session = null, $body = null, $cookies = null)
|
public function post($url, $data, $headers = null, $session = null, $body = null, $cookies = null)
|
||||||
{
|
{
|
||||||
$this->cssParser = null;
|
$this->cssParser = null;
|
||||||
$response = $this->mainSession->post($url, $data, $headers, $session, $body, $cookies);
|
$response = $this->mainSession->post($url, $data, $headers, $session, $body, $cookies);
|
||||||
if($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) {
|
if ($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) {
|
||||||
$response = $this->mainSession->followRedirection();
|
$response = $this->mainSession->followRedirection();
|
||||||
}
|
}
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit the form with the given HTML ID, filling it out with the given data.
|
* Submit the form with the given HTML ID, filling it out with the given data.
|
||||||
* Acts on the most recent response.
|
* Acts on the most recent response.
|
||||||
*
|
*
|
||||||
* Any data parameters have to be present in the form, with exact form field name
|
* Any data parameters have to be present in the form, with exact form field name
|
||||||
* and values, otherwise they are removed from the submission.
|
* and values, otherwise they are removed from the submission.
|
||||||
*
|
*
|
||||||
* Caution: Parameter names have to be formatted
|
* Caution: Parameter names have to be formatted
|
||||||
* as they are in the form submission, not as they are interpreted by PHP.
|
* as they are in the form submission, not as they are interpreted by PHP.
|
||||||
* Wrong: array('mycheckboxvalues' => array(1 => 'one', 2 => 'two'))
|
* Wrong: array('mycheckboxvalues' => array(1 => 'one', 2 => 'two'))
|
||||||
* Right: array('mycheckboxvalues[1]' => 'one', 'mycheckboxvalues[2]' => 'two')
|
* Right: array('mycheckboxvalues[1]' => 'one', 'mycheckboxvalues[2]' => 'two')
|
||||||
*
|
*
|
||||||
* @see http://www.simpletest.org/en/form_testing_documentation.html
|
* @see http://www.simpletest.org/en/form_testing_documentation.html
|
||||||
*
|
*
|
||||||
* @param string $formID HTML 'id' attribute of a form (loaded through a previous response)
|
* @param string $formID HTML 'id' attribute of a form (loaded through a previous response)
|
||||||
* @param string $button HTML 'name' attribute of the button (NOT the 'id' attribute)
|
* @param string $button HTML 'name' attribute of the button (NOT the 'id' attribute)
|
||||||
* @param array $data Map of GET/POST data.
|
* @param array $data Map of GET/POST data.
|
||||||
* @return HTTPResponse
|
* @return HTTPResponse
|
||||||
*/
|
*/
|
||||||
public function submitForm($formID, $button = null, $data = array())
|
public function submitForm($formID, $button = null, $data = array())
|
||||||
{
|
{
|
||||||
$this->cssParser = null;
|
$this->cssParser = null;
|
||||||
$response = $this->mainSession->submitForm($formID, $button, $data);
|
$response = $this->mainSession->submitForm($formID, $button, $data);
|
||||||
if($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) {
|
if ($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) {
|
||||||
$response = $this->mainSession->followRedirection();
|
$response = $this->mainSession->followRedirection();
|
||||||
}
|
}
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the most recent content
|
* Return the most recent content
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content()
|
||||||
{
|
{
|
||||||
return $this->mainSession->lastContent();
|
return $this->mainSession->lastContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an attribute in a SimpleXMLElement object by name.
|
* Find an attribute in a SimpleXMLElement object by name.
|
||||||
* @param SimpleXMLElement $object
|
* @param SimpleXMLElement $object
|
||||||
* @param string $attribute Name of attribute to find
|
* @param string $attribute Name of attribute to find
|
||||||
* @return SimpleXMLElement object of the attribute
|
* @return SimpleXMLElement object of the attribute
|
||||||
*/
|
*/
|
||||||
public function findAttribute($object, $attribute)
|
public function findAttribute($object, $attribute)
|
||||||
{
|
{
|
||||||
$found = false;
|
$found = false;
|
||||||
foreach($object->attributes() as $a => $b) {
|
foreach ($object->attributes() as $a => $b) {
|
||||||
if($a == $attribute) {
|
if ($a == $attribute) {
|
||||||
$found = $b;
|
$found = $b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $found;
|
return $found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a CSSContentParser for the most recent content.
|
* Return a CSSContentParser for the most recent content.
|
||||||
*
|
*
|
||||||
* @return CSSContentParser
|
* @return CSSContentParser
|
||||||
*/
|
*/
|
||||||
public function cssParser()
|
public function cssParser()
|
||||||
{
|
{
|
||||||
if (!$this->cssParser) {
|
if (!$this->cssParser) {
|
||||||
$this->cssParser = new CSSContentParser($this->mainSession->lastContent());
|
$this->cssParser = new CSSContentParser($this->mainSession->lastContent());
|
||||||
}
|
}
|
||||||
return $this->cssParser;
|
return $this->cssParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
||||||
* The given CSS selector will be applied to the HTML of the most recent page. The content of every matching tag
|
* The given CSS selector will be applied to the HTML of the most recent page. The content of every matching tag
|
||||||
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
||||||
*
|
*
|
||||||
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
||||||
*
|
*
|
||||||
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
||||||
* @param array|string $expectedMatches The content of at least one of the matched tags
|
* @param array|string $expectedMatches The content of at least one of the matched tags
|
||||||
* @throws PHPUnit_Framework_AssertionFailedError
|
* @param string $message
|
||||||
* @return boolean
|
* @throws PHPUnit_Framework_AssertionFailedError
|
||||||
*/
|
*/
|
||||||
public function assertPartialMatchBySelector($selector, $expectedMatches)
|
public function assertPartialMatchBySelector($selector, $expectedMatches, $message = null)
|
||||||
{
|
{
|
||||||
if (is_string($expectedMatches)) {
|
if (is_string($expectedMatches)) {
|
||||||
$expectedMatches = array($expectedMatches);
|
$expectedMatches = array($expectedMatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
$items = $this->cssParser()->getBySelector($selector);
|
$items = $this->cssParser()->getBySelector($selector);
|
||||||
|
|
||||||
$actuals = array();
|
$actuals = array();
|
||||||
if($items) foreach($items as $item) $actuals[trim(preg_replace("/\s+/", " ", (string)$item))] = true;
|
|
||||||
|
|
||||||
foreach($expectedMatches as $match) {
|
|
||||||
$this->assertTrue(
|
|
||||||
isset($actuals[$match]),
|
|
||||||
"Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'"
|
|
||||||
. implode("'\n'", $expectedMatches) . "'\n\n"
|
|
||||||
. "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'"
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
|
||||||
* The given CSS selector will be applied to the HTML of the most recent page. The full HTML of every matching tag
|
|
||||||
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
|
||||||
*
|
|
||||||
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
|
||||||
*
|
|
||||||
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
|
||||||
* @param array|string $expectedMatches The content of *all* matching tags as an array
|
|
||||||
* @throws PHPUnit_Framework_AssertionFailedError
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function assertExactMatchBySelector($selector, $expectedMatches)
|
|
||||||
{
|
|
||||||
if (is_string($expectedMatches)) {
|
|
||||||
$expectedMatches = array($expectedMatches);
|
|
||||||
}
|
|
||||||
|
|
||||||
$items = $this->cssParser()->getBySelector($selector);
|
|
||||||
|
|
||||||
$actuals = array();
|
|
||||||
if ($items) {
|
if ($items) {
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
$actuals[] = trim(preg_replace("/[ \n\r\t]+/", " ", $item. ''));
|
$actuals[trim(preg_replace('/\s+/', ' ', (string)$item))] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertTrue(
|
$message = $message ?:
|
||||||
$expectedMatches == $actuals,
|
"Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'"
|
||||||
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'"
|
. implode("'\n'", $expectedMatches) . "'\n\n"
|
||||||
. implode("'\n'", $expectedMatches) . "'\n\n"
|
. "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'";
|
||||||
. "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'"
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
foreach ($expectedMatches as $match) {
|
||||||
}
|
$this->assertTrue(isset($actuals[$match]), $message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
||||||
* The given CSS selector will be applied to the HTML of the most recent page. The content of every matching tag
|
* The given CSS selector will be applied to the HTML of the most recent page. The full HTML of every matching tag
|
||||||
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
||||||
*
|
*
|
||||||
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
||||||
*
|
*
|
||||||
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
||||||
* @param array|string $expectedMatches The content of at least one of the matched tags
|
* @param array|string $expectedMatches The content of *all* matching tags as an array
|
||||||
* @throws PHPUnit_Framework_AssertionFailedError
|
* @param string $message
|
||||||
* @return boolean
|
* @throws PHPUnit_Framework_AssertionFailedError
|
||||||
*/
|
*/
|
||||||
public function assertPartialHTMLMatchBySelector($selector, $expectedMatches)
|
public function assertExactMatchBySelector($selector, $expectedMatches, $message = null)
|
||||||
{
|
{
|
||||||
if (is_string($expectedMatches)) {
|
if (is_string($expectedMatches)) {
|
||||||
$expectedMatches = array($expectedMatches);
|
$expectedMatches = array($expectedMatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
$items = $this->cssParser()->getBySelector($selector);
|
$items = $this->cssParser()->getBySelector($selector);
|
||||||
|
|
||||||
$actuals = array();
|
$actuals = array();
|
||||||
if($items) {
|
if ($items) {
|
||||||
/** @var SimpleXMLElement $item */
|
foreach ($items as $item) {
|
||||||
foreach($items as $item) {
|
$actuals[] = trim(preg_replace('/\s+/', ' ', (string)$item));
|
||||||
$actuals[$item->asXML()] = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach($expectedMatches as $match) {
|
$message = $message ?:
|
||||||
$this->assertTrue(
|
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'"
|
||||||
isset($actuals[$match]),
|
. implode("'\n'", $expectedMatches) . "'\n\n"
|
||||||
"Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'"
|
. "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'";
|
||||||
. implode("'\n'", $expectedMatches) . "'\n\n"
|
|
||||||
. "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
$this->assertTrue($expectedMatches == $actuals, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
||||||
* The given CSS selector will be applied to the HTML of the most recent page. The full HTML of every matching tag
|
* The given CSS selector will be applied to the HTML of the most recent page. The content of every matching tag
|
||||||
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
||||||
*
|
*
|
||||||
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
||||||
*
|
*
|
||||||
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
||||||
* @param array|string $expectedMatches The content of *all* matched tags as an array
|
* @param array|string $expectedMatches The content of at least one of the matched tags
|
||||||
* @throws PHPUnit_Framework_AssertionFailedError
|
* @param string $message
|
||||||
*/
|
* @throws PHPUnit_Framework_AssertionFailedError
|
||||||
public function assertExactHTMLMatchBySelector($selector, $expectedMatches)
|
*/
|
||||||
|
public function assertPartialHTMLMatchBySelector($selector, $expectedMatches, $message = null)
|
||||||
{
|
{
|
||||||
$items = $this->cssParser()->getBySelector($selector);
|
if (is_string($expectedMatches)) {
|
||||||
|
$expectedMatches = array($expectedMatches);
|
||||||
|
}
|
||||||
|
|
||||||
$actuals = array();
|
$items = $this->cssParser()->getBySelector($selector);
|
||||||
if($items) {
|
|
||||||
/** @var SimpleXMLElement $item */
|
|
||||||
foreach($items as $item) {
|
|
||||||
$actuals[] = $item->asXML();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertTrue(
|
$actuals = array();
|
||||||
$expectedMatches == $actuals,
|
if ($items) {
|
||||||
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'"
|
/** @var SimpleXMLElement $item */
|
||||||
. implode("'\n'", $expectedMatches) . "'\n\n"
|
foreach ($items as $item) {
|
||||||
. "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'"
|
$actuals[$item->asXML()] = true;
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
$message = $message ?:
|
||||||
* Log in as the given member
|
"Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'"
|
||||||
*
|
. implode("'\n'", $expectedMatches) . "'\n\n"
|
||||||
* @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
|
. "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'";
|
||||||
*/
|
|
||||||
|
foreach ($expectedMatches as $match) {
|
||||||
|
$this->assertTrue(isset($actuals[$match]), $message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
|
||||||
|
* The given CSS selector will be applied to the HTML of the most recent page. The full HTML of every matching tag
|
||||||
|
* will be examined. The assertion fails if one of the expectedMatches fails to appear.
|
||||||
|
*
|
||||||
|
* Note: characters are stripped from the content; make sure that your assertions take this into account.
|
||||||
|
*
|
||||||
|
* @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
|
||||||
|
* @param array|string $expectedMatches The content of *all* matched tags as an array
|
||||||
|
* @param string $message
|
||||||
|
* @throws PHPUnit_Framework_AssertionFailedError
|
||||||
|
*/
|
||||||
|
public function assertExactHTMLMatchBySelector($selector, $expectedMatches, $message = null)
|
||||||
|
{
|
||||||
|
$items = $this->cssParser()->getBySelector($selector);
|
||||||
|
|
||||||
|
$actuals = array();
|
||||||
|
if ($items) {
|
||||||
|
/** @var SimpleXMLElement $item */
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$actuals[] = $item->asXML();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = $message ?:
|
||||||
|
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'"
|
||||||
|
. implode("'\n'", $expectedMatches) . "'\n\n"
|
||||||
|
. "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'";
|
||||||
|
|
||||||
|
$this->assertTrue($expectedMatches == $actuals, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log in as the given member
|
||||||
|
*
|
||||||
|
* @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
|
||||||
|
*/
|
||||||
public function logInAs($member)
|
public function logInAs($member)
|
||||||
{
|
{
|
||||||
if (is_object($member)) {
|
if (is_object($member)) {
|
||||||
@ -411,40 +409,40 @@ class FunctionalTest extends SapphireTest
|
|||||||
$memberID = $this->idFromFixture('SilverStripe\\Security\\Member', $member);
|
$memberID = $this->idFromFixture('SilverStripe\\Security\\Member', $member);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->session()->inst_set('loggedInAs', $memberID);
|
$this->session()->inst_set('loggedInAs', $memberID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the draft (stage) site for testing.
|
* Use the draft (stage) site for testing.
|
||||||
* This is helpful if you're not testing publication functionality and don't want "stage management" cluttering
|
* This is helpful if you're not testing publication functionality and don't want "stage management" cluttering
|
||||||
* your test.
|
* your test.
|
||||||
*
|
*
|
||||||
* @param bool $enabled toggle the use of the draft site
|
* @param bool $enabled toggle the use of the draft site
|
||||||
*/
|
*/
|
||||||
public function useDraftSite($enabled = true)
|
public function useDraftSite($enabled = true)
|
||||||
{
|
{
|
||||||
if($enabled) {
|
if ($enabled) {
|
||||||
$this->session()->inst_set('readingMode', 'Stage.Stage');
|
$this->session()->inst_set('readingMode', 'Stage.Stage');
|
||||||
$this->session()->inst_set('unsecuredDraftSite', true);
|
$this->session()->inst_set('unsecuredDraftSite', true);
|
||||||
} else {
|
} else {
|
||||||
$this->session()->inst_set('readingMode', 'Stage.Live');
|
$this->session()->inst_set('readingMode', 'Stage.Live');
|
||||||
$this->session()->inst_set('unsecuredDraftSite', false);
|
$this->session()->inst_set('unsecuredDraftSite', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function get_disable_themes()
|
public static function get_disable_themes()
|
||||||
{
|
{
|
||||||
return static::$disable_themes;
|
return static::$disable_themes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function get_use_draft_site()
|
public static function get_use_draft_site()
|
||||||
{
|
{
|
||||||
return static::$use_draft_site;
|
return static::$use_draft_site;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -519,7 +519,7 @@ class ConfirmedPasswordField extends FormField
|
|||||||
|
|
||||||
// With a valid user and password, check the password is correct
|
// With a valid user and password, check the password is correct
|
||||||
$checkResult = $member->checkPassword($this->currentPasswordValue);
|
$checkResult = $member->checkPassword($this->currentPasswordValue);
|
||||||
if (!$checkResult->valid()) {
|
if (!$checkResult->isValid()) {
|
||||||
$validator->validationError(
|
$validator->validationError(
|
||||||
$name,
|
$name,
|
||||||
_t(
|
_t(
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace SilverStripe\Forms;
|
namespace SilverStripe\Forms;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lets you include a nested group of fields inside a template.
|
* Lets you include a nested group of fields inside a template.
|
||||||
@ -147,34 +148,48 @@ class FieldGroup extends CompositeField
|
|||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function Message()
|
public function getMessage()
|
||||||
{
|
{
|
||||||
$fs = array();
|
$dataFields = array();
|
||||||
$this->collateDataFields($fs);
|
$this->collateDataFields($dataFields);
|
||||||
|
|
||||||
foreach ($fs as $subfield) {
|
/** @var FormField $subfield */
|
||||||
if ($m = $subfield->Message()) {
|
$messages = [];
|
||||||
$message[] = rtrim($m, ".");
|
foreach ($dataFields as $subfield) {
|
||||||
|
$message = $subfield->obj('Message')->forTemplate();
|
||||||
|
if ($message) {
|
||||||
|
$messages[] = rtrim($message, ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (isset($message)) ? implode(", ", $message) . "." : "";
|
if (!$messages) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(", ", $messages) . ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function MessageType()
|
public function getMessageType()
|
||||||
{
|
{
|
||||||
$fs = array();
|
$dataFields = array();
|
||||||
$this->collateDataFields($fs);
|
$this->collateDataFields($dataFields);
|
||||||
|
|
||||||
foreach ($fs as $subfield) {
|
/** @var FormField $subfield */
|
||||||
if ($m = $subfield->MessageType()) {
|
foreach ($dataFields as $subfield) {
|
||||||
$MessageType[] = $m;
|
$type = $subfield->getMessageType();
|
||||||
|
if ($type) {
|
||||||
|
return $type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (isset($MessageType)) ? implode(". ", $MessageType) : "";
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessageCast()
|
||||||
|
{
|
||||||
|
return ValidationResult::CAST_HTML;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3200
src/Forms/Form.php
3200
src/Forms/Form.php
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Forms;
|
namespace SilverStripe\Forms;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use SilverStripe\Control\HTTPRequest;
|
use SilverStripe\Control\HTTPRequest;
|
||||||
use SilverStripe\Control\HTTPResponse_Exception;
|
use SilverStripe\Control\HTTPResponse_Exception;
|
||||||
use SilverStripe\Core\Convert;
|
use SilverStripe\Core\Convert;
|
||||||
@ -15,10 +14,11 @@ use SilverStripe\Control\HTTP;
|
|||||||
use SilverStripe\Control\RequestHandler;
|
use SilverStripe\Control\RequestHandler;
|
||||||
use SilverStripe\Dev\Deprecation;
|
use SilverStripe\Dev\Deprecation;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\FieldType\DBField;
|
|
||||||
use SilverStripe\ORM\DataObjectInterface;
|
use SilverStripe\ORM\DataObjectInterface;
|
||||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||||
use SilverStripe\ORM\SS_List;
|
use SilverStripe\ORM\SS_List;
|
||||||
|
use SilverStripe\ORM\ValidationException;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\Security\SecurityToken;
|
use SilverStripe\Security\SecurityToken;
|
||||||
use SilverStripe\Security\NullSecurityToken;
|
use SilverStripe\Security\NullSecurityToken;
|
||||||
use SilverStripe\View\SSViewer;
|
use SilverStripe\View\SSViewer;
|
||||||
@ -66,1982 +66,1988 @@ use SilverStripe\View\SSViewer;
|
|||||||
*/
|
*/
|
||||||
class Form extends RequestHandler
|
class Form extends RequestHandler
|
||||||
{
|
{
|
||||||
|
use FormMessage;
|
||||||
|
|
||||||
const ENC_TYPE_URLENCODED = 'application/x-www-form-urlencoded';
|
/**
|
||||||
const ENC_TYPE_MULTIPART = 'multipart/form-data';
|
* Form submission data is URL encoded
|
||||||
|
*/
|
||||||
|
const ENC_TYPE_URLENCODED = 'application/x-www-form-urlencoded';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessed by Form.ss; modified by {@link formHtmlContent()}.
|
* Form submission data is multipart form
|
||||||
* A performance enhancement over the generate-the-form-tag-and-then-remove-it code that was there previously
|
*/
|
||||||
*
|
const ENC_TYPE_MULTIPART = 'multipart/form-data';
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
public $IncludeFormTag = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var FieldList
|
* Accessed by Form.ss; modified by {@link formHtmlContent()}.
|
||||||
*/
|
* A performance enhancement over the generate-the-form-tag-and-then-remove-it code that was there previously
|
||||||
protected $fields;
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $IncludeFormTag = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var FieldList
|
* @var FieldList
|
||||||
*/
|
*/
|
||||||
protected $actions;
|
protected $fields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Controller
|
* @var FieldList
|
||||||
*/
|
*/
|
||||||
protected $controller;
|
protected $actions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var Controller
|
||||||
*/
|
*/
|
||||||
protected $name;
|
protected $controller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Validator
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $validator;
|
protected $name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var callable {@see setValidationResponseCallback()}
|
* @var Validator
|
||||||
*/
|
*/
|
||||||
protected $validationResponseCallback;
|
protected $validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var callable {@see setValidationResponseCallback()}
|
||||||
*/
|
*/
|
||||||
protected $formMethod = "POST";
|
protected $validationResponseCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var boolean
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $strictFormMethodCheck = false;
|
protected $formMethod = "POST";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var DataObject|null $record Populated by {@link loadDataFrom()}.
|
* @var boolean
|
||||||
*/
|
*/
|
||||||
protected $record;
|
protected $strictFormMethodCheck = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps track of whether this form has a default action or not.
|
* @var DataObject|null $record Populated by {@link loadDataFrom()}.
|
||||||
* Set to false by $this->disableDefaultAction();
|
*/
|
||||||
*
|
protected $record;
|
||||||
* @var boolean
|
|
||||||
*/
|
|
||||||
protected $hasDefaultAction = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target attribute of form-tag.
|
* Keeps track of whether this form has a default action or not.
|
||||||
* Useful to open a new window upon
|
* Set to false by $this->disableDefaultAction();
|
||||||
* form submission.
|
*
|
||||||
*
|
* @var boolean
|
||||||
* @var string|null
|
*/
|
||||||
*/
|
protected $hasDefaultAction = true;
|
||||||
protected $target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legend value, to be inserted into the
|
* Target attribute of form-tag.
|
||||||
* <legend> element before the <fieldset>
|
* Useful to open a new window upon
|
||||||
* in Form.ss template.
|
* form submission.
|
||||||
*
|
*
|
||||||
* @var string|null
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
protected $legend;
|
protected $target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SS template to render this form HTML into.
|
* Legend value, to be inserted into the
|
||||||
* Default is "Form", but this can be changed to
|
* <legend> element before the <fieldset>
|
||||||
* another template for customisation.
|
* in Form.ss template.
|
||||||
*
|
*
|
||||||
* @see Form->setTemplate()
|
* @var string|null
|
||||||
* @var string|null
|
*/
|
||||||
*/
|
protected $legend;
|
||||||
protected $template;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var callable|null
|
* The SS template to render this form HTML into.
|
||||||
*/
|
* Default is "Form", but this can be changed to
|
||||||
protected $buttonClickedFunc;
|
* another template for customisation.
|
||||||
|
*
|
||||||
|
* @see Form->setTemplate()
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $template;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string|null
|
* @var callable|null
|
||||||
*/
|
*/
|
||||||
protected $message;
|
protected $buttonClickedFunc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string|null
|
* Should we redirect the user back down to the
|
||||||
*/
|
* the form on validation errors rather then just the page
|
||||||
protected $messageType;
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $redirectToFormOnValidationError = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should we redirect the user back down to the
|
* @var bool
|
||||||
* the form on validation errors rather then just the page
|
*/
|
||||||
*
|
protected $security = true;
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $redirectToFormOnValidationError = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var SecurityToken|null
|
||||||
*/
|
*/
|
||||||
protected $security = true;
|
protected $securityToken = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var SecurityToken|null
|
* @var array $extraClasses List of additional CSS classes for the form tag.
|
||||||
*/
|
*/
|
||||||
protected $securityToken = null;
|
protected $extraClasses = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array $extraClasses List of additional CSS classes for the form tag.
|
* @config
|
||||||
*/
|
* @var array $default_classes The default classes to apply to the Form
|
||||||
protected $extraClasses = array();
|
*/
|
||||||
|
private static $default_classes = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @var string|null
|
||||||
* @var array $default_classes The default classes to apply to the Form
|
*/
|
||||||
*/
|
protected $encType;
|
||||||
private static $default_classes = array();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string|null
|
* @var array Any custom form attributes set through {@link setAttributes()}.
|
||||||
*/
|
* Some attributes are calculated on the fly, so please use {@link getAttributes()} to access them.
|
||||||
protected $encType;
|
*/
|
||||||
|
protected $attributes = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array Any custom form attributes set through {@link setAttributes()}.
|
* @var array
|
||||||
* Some attributes are calculated on the fly, so please use {@link getAttributes()} to access them.
|
*/
|
||||||
*/
|
protected $validationExemptActions = array();
|
||||||
protected $attributes = array();
|
|
||||||
|
|
||||||
/**
|
private static $allowed_actions = array(
|
||||||
* @var array
|
'handleField',
|
||||||
*/
|
'httpSubmission',
|
||||||
protected $validationExemptActions = array();
|
'forTemplate',
|
||||||
|
);
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
private static $casting = array(
|
||||||
'handleField',
|
'AttributesHTML' => 'HTMLFragment',
|
||||||
'httpSubmission',
|
'FormAttributes' => 'HTMLFragment',
|
||||||
'forTemplate',
|
'FormName' => 'Text',
|
||||||
);
|
'Legend' => 'HTMLFragment',
|
||||||
|
);
|
||||||
|
|
||||||
private static $casting = array(
|
/**
|
||||||
'AttributesHTML' => 'HTMLFragment',
|
* @var FormTemplateHelper
|
||||||
'FormAttributes' => 'HTMLFragment',
|
*/
|
||||||
'MessageType' => 'Text',
|
private $templateHelper = null;
|
||||||
'Message' => 'HTMLFragment',
|
|
||||||
'FormName' => 'Text',
|
|
||||||
'Legend' => 'HTMLFragment',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var FormTemplateHelper
|
* @ignore
|
||||||
*/
|
*/
|
||||||
private $templateHelper = null;
|
private $htmlID = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
private $htmlID = null;
|
private $formActionPath = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private $formActionPath = false;
|
protected $securityTokenAdded = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* Create a new form, with the given fields an action buttons.
|
||||||
*/
|
*
|
||||||
protected $securityTokenAdded = false;
|
* @param Controller $controller The parent controller, necessary to create the appropriate form action tag.
|
||||||
|
* @param string $name The method on the controller that will return this form object.
|
||||||
/**
|
* @param FieldList $fields All of the fields in the form - a {@link FieldList} of {@link FormField} objects.
|
||||||
* Create a new form, with the given fields an action buttons.
|
* @param FieldList $actions All of the action buttons in the form - a {@link FieldLis} of
|
||||||
*
|
* {@link FormAction} objects
|
||||||
* @param Controller $controller The parent controller, necessary to create the appropriate form action tag.
|
* @param Validator|null $validator Override the default validator instance (Default: {@link RequiredFields})
|
||||||
* @param string $name The method on the controller that will return this form object.
|
*/
|
||||||
* @param FieldList $fields All of the fields in the form - a {@link FieldList} of {@link FormField} objects.
|
|
||||||
* @param FieldList $actions All of the action buttons in the form - a {@link FieldLis} of
|
|
||||||
* {@link FormAction} objects
|
|
||||||
* @param Validator|null $validator Override the default validator instance (Default: {@link RequiredFields})
|
|
||||||
*/
|
|
||||||
public function __construct($controller, $name, FieldList $fields, FieldList $actions, Validator $validator = null)
|
public function __construct($controller, $name, FieldList $fields, FieldList $actions, Validator $validator = null)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$fields->setForm($this);
|
$fields->setForm($this);
|
||||||
$actions->setForm($this);
|
$actions->setForm($this);
|
||||||
|
|
||||||
$this->fields = $fields;
|
$this->fields = $fields;
|
||||||
$this->actions = $actions;
|
$this->actions = $actions;
|
||||||
$this->controller = $controller;
|
$this->controller = $controller;
|
||||||
$this->setName($name);
|
$this->setName($name);
|
||||||
|
|
||||||
if (!$this->controller) {
|
if (!$this->controller) {
|
||||||
user_error("$this->class form created without a controller", E_USER_ERROR);
|
user_error("$this->class form created without a controller", E_USER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form validation
|
// Form validation
|
||||||
$this->validator = ($validator) ? $validator : new RequiredFields();
|
$this->validator = ($validator) ? $validator : new RequiredFields();
|
||||||
$this->validator->setForm($this);
|
$this->validator->setForm($this);
|
||||||
|
|
||||||
// Form error controls
|
// Form error controls
|
||||||
$this->setupFormErrors();
|
$this->restoreFormState();
|
||||||
|
|
||||||
// Check if CSRF protection is enabled, either on the parent controller or from the default setting. Note that
|
// Check if CSRF protection is enabled, either on the parent controller or from the default setting. Note that
|
||||||
// method_exists() is used as some controllers (e.g. GroupTest) do not always extend from Object.
|
// method_exists() is used as some controllers (e.g. GroupTest) do not always extend from Object.
|
||||||
if(method_exists($controller, 'securityTokenEnabled') || (method_exists($controller, 'hasMethod')
|
if (method_exists($controller, 'securityTokenEnabled') || (method_exists($controller, 'hasMethod')
|
||||||
&& $controller->hasMethod('securityTokenEnabled'))) {
|
&& $controller->hasMethod('securityTokenEnabled'))) {
|
||||||
$securityEnabled = $controller->securityTokenEnabled();
|
$securityEnabled = $controller->securityTokenEnabled();
|
||||||
} else {
|
} else {
|
||||||
$securityEnabled = SecurityToken::is_enabled();
|
$securityEnabled = SecurityToken::is_enabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->securityToken = ($securityEnabled) ? new SecurityToken() : new NullSecurityToken();
|
$this->securityToken = ($securityEnabled) ? new SecurityToken() : new NullSecurityToken();
|
||||||
|
|
||||||
$this->setupDefaultClasses();
|
$this->setupDefaultClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $url_handlers = array(
|
private static $url_handlers = array(
|
||||||
'field/$FieldName!' => 'handleField',
|
'field/$FieldName!' => 'handleField',
|
||||||
'POST ' => 'httpSubmission',
|
'POST ' => 'httpSubmission',
|
||||||
'GET ' => 'httpSubmission',
|
'GET ' => 'httpSubmission',
|
||||||
'HEAD ' => 'httpSubmission',
|
'HEAD ' => 'httpSubmission',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take errors from a ValidationResult and populate the form with the appropriate message.
|
* Load form state from session state
|
||||||
*
|
* @return $this
|
||||||
* @param ValidationResult $result The erroneous ValidationResult. If none passed, this will be atken
|
*/
|
||||||
* from the session
|
public function restoreFormState()
|
||||||
*/
|
{
|
||||||
public function setupFormErrors($result = null, $data = null) {
|
// Restore messages
|
||||||
if(!$result) $result = Session::get("FormInfo.{$this->FormName()}.result");
|
$result = $this->getSessionValidationResult();
|
||||||
if(!$result) return;
|
if (isset($result)) {
|
||||||
|
$this->loadMessagesFrom($result);
|
||||||
|
}
|
||||||
|
|
||||||
foreach($result->fieldErrors() as $fieldName => $fieldError) {
|
// load data in from previous submission upon error
|
||||||
$field = $this->fields->dataFieldByName($fieldName);
|
$data = $this->getSessionData();
|
||||||
$field->setError($fieldError['message'], $fieldError['messageType']);
|
if (isset($data)) {
|
||||||
}
|
$this->loadDataFrom($data);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
//don't escape the HTML as it should have been escaped when adding it to the validation result
|
/**
|
||||||
$this->setMessage($result->overallMessage(), $result->valid() ? 'good' : 'bad', false);
|
* Flush persistant form state details
|
||||||
|
*/
|
||||||
|
public function clearFormState()
|
||||||
|
{
|
||||||
|
Session::clear("FormInfo.{$this->FormName()}.result");
|
||||||
|
Session::clear("FormInfo.{$this->FormName()}.data");
|
||||||
|
}
|
||||||
|
|
||||||
// load data in from previous submission upon error
|
/**
|
||||||
if(!$data) $data = Session::get("FormInfo.{$this->FormName()}.data");
|
* Return any form data stored in the session
|
||||||
if($data) $this->loadDataFrom($data);
|
*
|
||||||
}
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getSessionData()
|
||||||
|
{
|
||||||
|
return Session::get("FormInfo.{$this->FormName()}.data");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save information to the session to be picked up by {@link setUpFormErrors()}
|
* Store the given form data in the session
|
||||||
*/
|
*
|
||||||
public function saveFormErrorsToSession($result = null, $data = null) {
|
* @param array $data
|
||||||
Session::set("FormInfo.{$this->FormName()}.result", $result);
|
*/
|
||||||
Session::set("FormInfo.{$this->FormName()}.data", $data);
|
public function setSessionData($data)
|
||||||
}
|
{
|
||||||
|
Session::set("FormInfo.{$this->FormName()}.data", $data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set up the default classes for the form. This is done on construct so that the default classes can be removed
|
* Return any ValidationResult instance stored for this object
|
||||||
* after instantiation
|
*
|
||||||
*/
|
* @return ValidationResult The ValidationResult object stored in the session
|
||||||
|
*/
|
||||||
|
public function getSessionValidationResult()
|
||||||
|
{
|
||||||
|
$resultData = Session::get("FormInfo.{$this->FormName()}.result");
|
||||||
|
if (isset($resultData)) {
|
||||||
|
return unserialize($resultData);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the ValidationResult in the session to be used with the next view of this form.
|
||||||
|
* @param ValidationResult $result The result to save
|
||||||
|
* @param bool $combineWithExisting If true, then this will be added to the existing result.
|
||||||
|
*/
|
||||||
|
public function setSessionValidationResult(ValidationResult $result, $combineWithExisting = false)
|
||||||
|
{
|
||||||
|
// Combine with existing result
|
||||||
|
if ($combineWithExisting) {
|
||||||
|
$existingResult = $this->getSessionValidationResult();
|
||||||
|
if ($existingResult) {
|
||||||
|
if ($result) {
|
||||||
|
$existingResult->combineAnd($result);
|
||||||
|
} else {
|
||||||
|
$result = $existingResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialise
|
||||||
|
$resultData = $result ? serialize($result) : null;
|
||||||
|
Session::set("FormInfo.{$this->FormName()}.result", $resultData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearMessage()
|
||||||
|
{
|
||||||
|
$this->setMessage(null);
|
||||||
|
$this->clearFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate this form with messages from the given ValidationResult.
|
||||||
|
* Note: This will not clear any pre-existing messages
|
||||||
|
*
|
||||||
|
* @param ValidationResult $result
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function loadMessagesFrom($result)
|
||||||
|
{
|
||||||
|
// Set message on either a field or the parent form
|
||||||
|
foreach ($result->getMessages() as $message) {
|
||||||
|
$fieldName = $message['fieldName'];
|
||||||
|
if ($fieldName) {
|
||||||
|
$owner = $this->fields->dataFieldByName($fieldName) ?: $this;
|
||||||
|
} else {
|
||||||
|
$owner = $this;
|
||||||
|
}
|
||||||
|
$owner->setMessage($message['message'], $message['messageType'], $message['messageCast']);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set message on a given field name. This message will not persist via redirect.
|
||||||
|
*
|
||||||
|
* @param string $fieldName
|
||||||
|
* @param string $message
|
||||||
|
* @param string $messageType
|
||||||
|
* @param string $messageCast
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setFieldMessage(
|
||||||
|
$fieldName,
|
||||||
|
$message,
|
||||||
|
$messageType = ValidationResult::TYPE_ERROR,
|
||||||
|
$messageCast = ValidationResult::CAST_TEXT
|
||||||
|
) {
|
||||||
|
$field = $this->fields->dataFieldByName($fieldName);
|
||||||
|
if ($field) {
|
||||||
|
$field->setMessage($message, $messageType, $messageCast);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function castingHelper($field)
|
||||||
|
{
|
||||||
|
// Override casting for field message
|
||||||
|
if (strcasecmp($field, 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
|
||||||
|
return $helper;
|
||||||
|
}
|
||||||
|
return parent::castingHelper($field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set up the default classes for the form. This is done on construct so that the default classes can be removed
|
||||||
|
* after instantiation
|
||||||
|
*/
|
||||||
protected function setupDefaultClasses()
|
protected function setupDefaultClasses()
|
||||||
{
|
{
|
||||||
$defaultClasses = self::config()->get('default_classes');
|
$defaultClasses = self::config()->get('default_classes');
|
||||||
if ($defaultClasses) {
|
if ($defaultClasses) {
|
||||||
foreach ($defaultClasses as $class) {
|
foreach ($defaultClasses as $class) {
|
||||||
$this->addExtraClass($class);
|
$this->addExtraClass($class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a form submission. GET and POST requests behave identically.
|
* Handle a form submission. GET and POST requests behave identically.
|
||||||
* Populates the form with {@link loadDataFrom()}, calls {@link validate()},
|
* Populates the form with {@link loadDataFrom()}, calls {@link validate()},
|
||||||
* and only triggers the requested form action/method
|
* and only triggers the requested form action/method
|
||||||
* if the form is valid.
|
* if the form is valid.
|
||||||
*
|
*
|
||||||
* @param HTTPRequest $request
|
* @param HTTPRequest $request
|
||||||
* @throws HTTPResponse_Exception
|
* @return HTTPResponse
|
||||||
*/
|
* @throws HTTPResponse_Exception
|
||||||
|
*/
|
||||||
public function httpSubmission($request)
|
public function httpSubmission($request)
|
||||||
{
|
{
|
||||||
// Strict method check
|
// Strict method check
|
||||||
if($this->strictFormMethodCheck) {
|
if ($this->strictFormMethodCheck) {
|
||||||
// Throws an error if the method is bad...
|
// Throws an error if the method is bad...
|
||||||
if($this->formMethod != $request->httpMethod()) {
|
if ($this->formMethod != $request->httpMethod()) {
|
||||||
$response = Controller::curr()->getResponse();
|
$response = Controller::curr()->getResponse();
|
||||||
$response->addHeader('Allow', $this->formMethod);
|
$response->addHeader('Allow', $this->formMethod);
|
||||||
$this->httpError(405, _t("Form.BAD_METHOD", "This form requires a ".$this->formMethod." submission"));
|
$this->httpError(405, _t("Form.BAD_METHOD", "This form requires a ".$this->formMethod." submission"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...and only uses the variables corresponding to that method type
|
// ...and only uses the variables corresponding to that method type
|
||||||
$vars = $this->formMethod == 'GET' ? $request->getVars() : $request->postVars();
|
$vars = $this->formMethod == 'GET' ? $request->getVars() : $request->postVars();
|
||||||
} else {
|
} else {
|
||||||
$vars = $request->requestVars();
|
$vars = $request->requestVars();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we only process saveable fields (non structural, readonly, or disabled)
|
// Ensure we only process saveable fields (non structural, readonly, or disabled)
|
||||||
$allowedFields = array_keys($this->Fields()->saveableFields());
|
$allowedFields = array_keys($this->Fields()->saveableFields());
|
||||||
|
|
||||||
// Populate the form
|
// Populate the form
|
||||||
$this->loadDataFrom($vars, true, $allowedFields);
|
$this->loadDataFrom($vars, true, $allowedFields);
|
||||||
|
|
||||||
// Protection against CSRF attacks
|
// Protection against CSRF attacks
|
||||||
$token = $this->getSecurityToken();
|
// @todo Move this to SecurityTokenField::validate()
|
||||||
if( ! $token->checkRequest($request)) {
|
$token = $this->getSecurityToken();
|
||||||
$securityID = $token->getName();
|
if (! $token->checkRequest($request)) {
|
||||||
if (empty($vars[$securityID])) {
|
$securityID = $token->getName();
|
||||||
|
if (empty($vars[$securityID])) {
|
||||||
$this->httpError(400, _t(
|
$this->httpError(400, _t(
|
||||||
"Form.CSRF_FAILED_MESSAGE",
|
"Form.CSRF_FAILED_MESSAGE",
|
||||||
"There seems to have been a technical problem. Please click the back button, ".
|
"There seems to have been a technical problem. Please click the back button, ".
|
||||||
"refresh your browser, and try again."
|
"refresh your browser, and try again."
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// Clear invalid token on refresh
|
// Clear invalid token on refresh
|
||||||
$data = $this->getData();
|
$this->clearFormState();
|
||||||
unset($data[$securityID]);
|
$data = $this->getData();
|
||||||
Session::set("FormInfo.{$this->FormName()}.data", $data);
|
unset($data[$securityID]);
|
||||||
Session::set("FormInfo.{$this->FormName()}.errors", array());
|
$this->setSessionData($data);
|
||||||
$this->sessionMessage(
|
$this->sessionError(_t(
|
||||||
_t("Form.CSRF_EXPIRED_MESSAGE", "Your session has expired. Please re-submit the form."),
|
"Form.CSRF_EXPIRED_MESSAGE",
|
||||||
"warning"
|
"Your session has expired. Please re-submit the form."
|
||||||
);
|
));
|
||||||
return $this->controller->redirectBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the action button clicked
|
// Return the user
|
||||||
$funcName = null;
|
return $this->controller->redirectBack();
|
||||||
foreach($vars as $paramName => $paramVal) {
|
}
|
||||||
if(substr($paramName,0,7) == 'action_') {
|
}
|
||||||
// Break off querystring arguments included in the action
|
|
||||||
if(strpos($paramName,'?') !== false) {
|
|
||||||
list($paramName, $paramVars) = explode('?', $paramName, 2);
|
|
||||||
$newRequestParams = array();
|
|
||||||
parse_str($paramVars, $newRequestParams);
|
|
||||||
$vars = array_merge((array)$vars, (array)$newRequestParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup action_, _x and _y from image fields
|
// Determine the action button clicked
|
||||||
$funcName = preg_replace(array('/^action_/','/_x$|_y$/'),'',$paramName);
|
$funcName = null;
|
||||||
break;
|
foreach ($vars as $paramName => $paramVal) {
|
||||||
}
|
if (substr($paramName, 0, 7) == 'action_') {
|
||||||
}
|
// Break off querystring arguments included in the action
|
||||||
|
if (strpos($paramName, '?') !== false) {
|
||||||
|
list($paramName, $paramVars) = explode('?', $paramName, 2);
|
||||||
|
$newRequestParams = array();
|
||||||
|
parse_str($paramVars, $newRequestParams);
|
||||||
|
$vars = array_merge((array)$vars, (array)$newRequestParams);
|
||||||
|
}
|
||||||
|
|
||||||
// If the action wasn't set, choose the default on the form.
|
// Cleanup action_, _x and _y from image fields
|
||||||
if(!isset($funcName) && $defaultAction = $this->defaultAction()){
|
$funcName = preg_replace(array('/^action_/','/_x$|_y$/'), '', $paramName);
|
||||||
$funcName = $defaultAction->actionName();
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(isset($funcName)) {
|
// If the action wasn't set, choose the default on the form.
|
||||||
$this->setButtonClicked($funcName);
|
if (!isset($funcName) && $defaultAction = $this->defaultAction()) {
|
||||||
}
|
$funcName = $defaultAction->actionName();
|
||||||
|
}
|
||||||
|
|
||||||
// Permission checks (first on controller, then falling back to form)
|
if (isset($funcName)) {
|
||||||
|
$this->setButtonClicked($funcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission checks (first on controller, then falling back to form)
|
||||||
if (// Ensure that the action is actually a button or method on the form,
|
if (// Ensure that the action is actually a button or method on the form,
|
||||||
// and not just a method on the controller.
|
// and not just a method on the controller.
|
||||||
$this->controller->hasMethod($funcName)
|
$this->controller->hasMethod($funcName)
|
||||||
&& !$this->controller->checkAccessAction($funcName)
|
&& !$this->controller->checkAccessAction($funcName)
|
||||||
// If a button exists, allow it on the controller
|
// If a button exists, allow it on the controller
|
||||||
// buttonClicked() validates that the action set above is valid
|
// buttonClicked() validates that the action set above is valid
|
||||||
&& !$this->buttonClicked()
|
&& !$this->buttonClicked()
|
||||||
) {
|
) {
|
||||||
return $this->httpError(
|
return $this->httpError(
|
||||||
403,
|
403,
|
||||||
sprintf('Action "%s" not allowed on controller (Class: %s)', $funcName, get_class($this->controller))
|
sprintf('Action "%s" not allowed on controller (Class: %s)', $funcName, get_class($this->controller))
|
||||||
);
|
);
|
||||||
} elseif ($this->hasMethod($funcName)
|
} elseif ($this->hasMethod($funcName)
|
||||||
&& !$this->checkAccessAction($funcName)
|
&& !$this->checkAccessAction($funcName)
|
||||||
// No checks for button existence or $allowed_actions is performed -
|
// No checks for button existence or $allowed_actions is performed -
|
||||||
// all form methods are callable (e.g. the legacy "callfieldmethod()")
|
// all form methods are callable (e.g. the legacy "callfieldmethod()")
|
||||||
) {
|
) {
|
||||||
return $this->httpError(
|
return $this->httpError(
|
||||||
403,
|
403,
|
||||||
sprintf('Action "%s" not allowed on form (Name: "%s")', $funcName, $this->name)
|
sprintf('Action "%s" not allowed on form (Name: "%s")', $funcName, $this->name)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO : Once we switch to a stricter policy regarding allowed_actions (meaning actions must be set
|
|
||||||
// explicitly in allowed_actions in order to run)
|
|
||||||
// Uncomment the following for checking security against running actions on form fields
|
|
||||||
/* else {
|
|
||||||
// Try to find a field that has the action, and allows it
|
|
||||||
$fieldsHaveMethod = false;
|
|
||||||
foreach ($this->Fields() as $field){
|
|
||||||
if ($field->hasMethod($funcName) && $field->checkAccessAction($funcName)) {
|
|
||||||
$fieldsHaveMethod = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$fieldsHaveMethod) {
|
|
||||||
return $this->httpError(
|
|
||||||
403,
|
|
||||||
sprintf('Action "%s" not allowed on any fields of form (Name: "%s")', $funcName, $this->Name())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Action handlers may throw ValidationExceptions.
|
// Action handlers may throw ValidationExceptions.
|
||||||
try {
|
try {
|
||||||
// Or we can use the Valiator attached to the form
|
// Or we can use the Valiator attached to the form
|
||||||
$result = $this->validationResult();
|
$result = $this->validationResult();
|
||||||
if(!$result->valid()) {
|
if (!$result->isValid()) {
|
||||||
return $this->getValidationErrorResponse($result);
|
return $this->getValidationErrorResponse($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, try a handler method on the controller (has been checked for allowed_actions above already)
|
// First, try a handler method on the controller (has been checked for allowed_actions above already)
|
||||||
if($this->controller->hasMethod($funcName)) {
|
if ($this->controller->hasMethod($funcName)) {
|
||||||
return $this->controller->$funcName($vars, $this, $request);
|
return $this->controller->$funcName($vars, $this, $request);
|
||||||
// Otherwise, try a handler method on the form object.
|
}
|
||||||
} elseif($this->hasMethod($funcName)) {
|
|
||||||
return $this->$funcName($vars, $this, $request);
|
|
||||||
} elseif($field = $this->checkFieldsForAction($this->Fields(), $funcName)) {
|
|
||||||
return $field->$funcName($vars, $this, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(ValidationException $e) {
|
// Otherwise, try a handler method on the form object.
|
||||||
// The ValdiationResult contains all the relevant metadata
|
if ($this->hasMethod($funcName)) {
|
||||||
$result = $e->getResult();
|
return $this->$funcName($vars, $this, $request);
|
||||||
return $this->getValidationErrorResponse($result);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// First, try a handler method on the controller (has been checked for allowed_actions above already)
|
// Check for inline actions
|
||||||
if($this->controller->hasMethod($funcName)) {
|
if ($field = $this->checkFieldsForAction($this->Fields(), $funcName)) {
|
||||||
return $this->controller->$funcName($vars, $this, $request);
|
return $field->$funcName($vars, $this, $request);
|
||||||
// Otherwise, try a handler method on the form object.
|
}
|
||||||
} elseif($this->hasMethod($funcName)) {
|
} catch (ValidationException $e) {
|
||||||
return $this->$funcName($vars, $this, $request);
|
// The ValdiationResult contains all the relevant metadata
|
||||||
} elseif($field = $this->checkFieldsForAction($this->Fields(), $funcName)) {
|
$result = $e->getResult();
|
||||||
return $field->$funcName($vars, $this, $request);
|
$this->loadMessagesFrom($result);
|
||||||
}
|
return $this->getValidationErrorResponse($result);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->httpError(404);
|
return $this->httpError(404);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $action
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function checkAccessAction($action)
|
|
||||||
{
|
|
||||||
if (parent::checkAccessAction($action)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$actions = $this->getAllActions();
|
|
||||||
foreach ($actions as $formAction) {
|
|
||||||
if ($formAction->actionName() === $action) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always allow actions on fields
|
|
||||||
$field = $this->checkFieldsForAction($this->Fields(), $action);
|
|
||||||
if ($field && $field->checkAccessAction($action)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return callable
|
|
||||||
*/
|
|
||||||
public function getValidationResponseCallback()
|
|
||||||
{
|
|
||||||
return $this->validationResponseCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overrules validation error behaviour in {@link httpSubmission()}
|
|
||||||
* when validation has failed. Useful for optional handling of a certain accepted content type.
|
|
||||||
*
|
|
||||||
* The callback can opt out of handling specific responses by returning NULL,
|
|
||||||
* in which case the default form behaviour will kick in.
|
|
||||||
*
|
|
||||||
* @param $callback
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setValidationResponseCallback($callback)
|
|
||||||
{
|
|
||||||
$this->validationResponseCallback = $callback;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the appropriate response up the controller chain
|
|
||||||
* if {@link validate()} fails (which is checked prior to executing any form actions).
|
|
||||||
* By default, returns different views for ajax/non-ajax request, and
|
|
||||||
* handles 'application/json' requests with a JSON object containing the error messages.
|
|
||||||
* Behaviour can be influenced by setting {@link $redirectToFormOnValidationError},
|
|
||||||
* and can be overruled by setting {@link $validationResponseCallback}.
|
|
||||||
*
|
|
||||||
* @param ValidationResult $result
|
|
||||||
* @return HTTPResponse|string
|
|
||||||
*/
|
|
||||||
protected function getValidationErrorResponse(ValidationResult $result) {
|
|
||||||
$callback = $this->getValidationResponseCallback();
|
|
||||||
if($callback && $callbackResponse = $callback($result)) {
|
|
||||||
return $callbackResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
$request = $this->getRequest();
|
|
||||||
if($request->isAjax()) {
|
|
||||||
// Special case for legacy Validator.js implementation
|
|
||||||
// (assumes eval'ed javascript collected through FormResponse)
|
|
||||||
$acceptType = $request->getHeader('Accept');
|
|
||||||
if (strpos($acceptType, 'application/json') !== false) {
|
|
||||||
// Send validation errors back as JSON with a flag at the start
|
|
||||||
$response = new HTTPResponse(Convert::array2json($result->getErrorMetaData()));
|
|
||||||
$response->addHeader('Content-Type', 'application/json');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$this->setupFormErrors($result, $this->getData());
|
|
||||||
// Send the newly rendered form tag as HTML
|
|
||||||
$response = new HTTPResponse($this->forTemplate());
|
|
||||||
$response->addHeader('Content-Type', 'text/html');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Save the relevant information in the session
|
|
||||||
$this->saveFormErrorsToSession($result, $this->getData());
|
|
||||||
|
|
||||||
// Redirect back to the form
|
|
||||||
if($this->getRedirectToFormOnValidationError()) {
|
|
||||||
if($pageURL = $request->getHeader('Referer')) {
|
|
||||||
if(Director::is_site_url($pageURL)) {
|
|
||||||
// Remove existing pragmas
|
|
||||||
$pageURL = preg_replace('/(#.*)/', '', $pageURL);
|
|
||||||
$pageURL = Director::absoluteURL($pageURL, true);
|
|
||||||
return $this->controller->redirect($pageURL . '#' . $this->FormName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->controller->redirectBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fields can have action to, let's check if anyone of the responds to $funcname them
|
|
||||||
*
|
|
||||||
* @param SS_List|array $fields
|
|
||||||
* @param callable $funcName
|
|
||||||
* @return FormField
|
|
||||||
*/
|
|
||||||
protected function checkFieldsForAction($fields, $funcName)
|
|
||||||
{
|
|
||||||
foreach($fields as $field){
|
|
||||||
/** @skipUpgrade */
|
|
||||||
if(method_exists($field, 'FieldList')) {
|
|
||||||
if($field = $this->checkFieldsForAction($field->FieldList(), $funcName)) {
|
|
||||||
return $field;
|
|
||||||
}
|
|
||||||
} elseif ($field->hasMethod($funcName) && $field->checkAccessAction($funcName)) {
|
|
||||||
return $field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a field request.
|
|
||||||
* Uses {@link Form->dataFieldByName()} to find a matching field,
|
|
||||||
* and falls back to {@link FieldList->fieldByName()} to look
|
|
||||||
* for tabs instead. This means that if you have a tab and a
|
|
||||||
* formfield with the same name, this method gives priority
|
|
||||||
* to the formfield.
|
|
||||||
*
|
|
||||||
* @param HTTPRequest $request
|
|
||||||
* @return FormField
|
|
||||||
*/
|
|
||||||
public function handleField($request)
|
|
||||||
{
|
|
||||||
$field = $this->Fields()->dataFieldByName($request->param('FieldName'));
|
|
||||||
|
|
||||||
if($field) {
|
|
||||||
return $field;
|
|
||||||
} else {
|
|
||||||
// falling back to fieldByName, e.g. for getting tabs
|
|
||||||
return $this->Fields()->fieldByName($request->param('FieldName'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert this form into a readonly form
|
|
||||||
*/
|
|
||||||
public function makeReadonly()
|
|
||||||
{
|
|
||||||
$this->transform(new ReadonlyTransformation());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set whether the user should be redirected back down to the
|
|
||||||
* form on the page upon validation errors in the form or if
|
|
||||||
* they just need to redirect back to the page
|
|
||||||
*
|
|
||||||
* @param bool $bool Redirect to form on error?
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setRedirectToFormOnValidationError($bool)
|
|
||||||
{
|
|
||||||
$this->redirectToFormOnValidationError = $bool;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get whether the user should be redirected back down to the
|
|
||||||
* form on the page upon validation errors
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function getRedirectToFormOnValidationError()
|
|
||||||
{
|
|
||||||
return $this->redirectToFormOnValidationError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a plain text error message to a field on this form. It will be saved into the session
|
|
||||||
* and used the next time this form is displayed.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2
|
|
||||||
*/
|
|
||||||
public function addErrorMessage($fieldName, $message, $messageType) {
|
|
||||||
Deprecation::notice('3.2', 'Throw a ValidationException instead.');
|
|
||||||
|
|
||||||
$this->getSessionValidationResult()->addFieldError($fieldName, $message, $messageType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param FormTransformation $trans
|
|
||||||
*/
|
|
||||||
public function transform(FormTransformation $trans)
|
|
||||||
{
|
|
||||||
$newFields = new FieldList();
|
|
||||||
foreach($this->fields as $field) {
|
|
||||||
$newFields->push($field->transform($trans));
|
|
||||||
}
|
|
||||||
$this->fields = $newFields;
|
|
||||||
|
|
||||||
$newActions = new FieldList();
|
|
||||||
foreach($this->actions as $action) {
|
|
||||||
$newActions->push($action->transform($trans));
|
|
||||||
}
|
|
||||||
$this->actions = $newActions;
|
|
||||||
|
|
||||||
|
|
||||||
// We have to remove validation, if the fields are not editable ;-)
|
|
||||||
if ($this->validator) {
|
|
||||||
$this->validator->removeValidation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@link Validator} attached to this form.
|
* @param string $action
|
||||||
* @return Validator
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
public function checkAccessAction($action)
|
||||||
|
{
|
||||||
|
if (parent::checkAccessAction($action)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$actions = $this->getAllActions();
|
||||||
|
foreach ($actions as $formAction) {
|
||||||
|
if ($formAction->actionName() === $action) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always allow actions on fields
|
||||||
|
$field = $this->checkFieldsForAction($this->Fields(), $action);
|
||||||
|
if ($field && $field->checkAccessAction($action)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return callable
|
||||||
|
*/
|
||||||
|
public function getValidationResponseCallback()
|
||||||
|
{
|
||||||
|
return $this->validationResponseCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrules validation error behaviour in {@link httpSubmission()}
|
||||||
|
* when validation has failed. Useful for optional handling of a certain accepted content type.
|
||||||
|
*
|
||||||
|
* The callback can opt out of handling specific responses by returning NULL,
|
||||||
|
* in which case the default form behaviour will kick in.
|
||||||
|
*
|
||||||
|
* @param $callback
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setValidationResponseCallback($callback)
|
||||||
|
{
|
||||||
|
$this->validationResponseCallback = $callback;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the appropriate response up the controller chain
|
||||||
|
* if {@link validate()} fails (which is checked prior to executing any form actions).
|
||||||
|
* By default, returns different views for ajax/non-ajax request, and
|
||||||
|
* handles 'application/json' requests with a JSON object containing the error messages.
|
||||||
|
* Behaviour can be influenced by setting {@link $redirectToFormOnValidationError},
|
||||||
|
* and can be overruled by setting {@link $validationResponseCallback}.
|
||||||
|
*
|
||||||
|
* @param ValidationResult $result
|
||||||
|
* @return HTTPResponse
|
||||||
|
*/
|
||||||
|
protected function getValidationErrorResponse(ValidationResult $result)
|
||||||
|
{
|
||||||
|
// Check for custom handling mechanism
|
||||||
|
$callback = $this->getValidationResponseCallback();
|
||||||
|
if ($callback && $callbackResponse = call_user_func($callback, $result)) {
|
||||||
|
return $callbackResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if handling via ajax
|
||||||
|
if ($this->getRequest()->isAjax()) {
|
||||||
|
return $this->getAjaxErrorResponse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prior to redirection, persist this result in session to re-display on redirect
|
||||||
|
$this->setSessionValidationResult($result);
|
||||||
|
$this->setSessionData($this->getData());
|
||||||
|
|
||||||
|
// Determine redirection method
|
||||||
|
if ($this->getRedirectToFormOnValidationError() && ($pageURL = $this->getRedirectReferer())) {
|
||||||
|
return $this->controller->redirect($pageURL . '#' . $this->FormName());
|
||||||
|
}
|
||||||
|
return $this->controller->redirectBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build HTTP error response for ajax requests
|
||||||
|
*
|
||||||
|
* @internal called from {@see Form::getValidationErrorResponse}
|
||||||
|
* @param ValidationResult $result
|
||||||
|
* @return HTTPResponse
|
||||||
|
*/
|
||||||
|
protected function getAjaxErrorResponse(ValidationResult $result)
|
||||||
|
{
|
||||||
|
// Ajax form submissions accept json encoded errors by default
|
||||||
|
$acceptType = $this->getRequest()->getHeader('Accept');
|
||||||
|
if (strpos($acceptType, 'application/json') !== false) {
|
||||||
|
// Send validation errors back as JSON with a flag at the start
|
||||||
|
$response = new HTTPResponse(Convert::array2json($result->getMessages()));
|
||||||
|
$response->addHeader('Content-Type', 'application/json');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the newly rendered form tag as HTML
|
||||||
|
$this->loadMessagesFrom($result);
|
||||||
|
$response = new HTTPResponse($this->forTemplate());
|
||||||
|
$response->addHeader('Content-Type', 'text/html');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get referrer to redirect back to and safely validates it
|
||||||
|
*
|
||||||
|
* @internal called from {@see Form::getValidationErrorResponse}
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function getRedirectReferer()
|
||||||
|
{
|
||||||
|
$pageURL = $this->getRequest()->getHeader('Referer');
|
||||||
|
if (!$pageURL) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!Director::is_site_url($pageURL)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing pragmas
|
||||||
|
$pageURL = preg_replace('/(#.*)/', '', $pageURL);
|
||||||
|
return Director::absoluteURL($pageURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields can have action to, let's check if anyone of the responds to $funcname them
|
||||||
|
*
|
||||||
|
* @param SS_List|array $fields
|
||||||
|
* @param callable $funcName
|
||||||
|
* @return FormField
|
||||||
|
*/
|
||||||
|
protected function checkFieldsForAction($fields, $funcName)
|
||||||
|
{
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
/** @skipUpgrade */
|
||||||
|
if (method_exists($field, 'FieldList')) {
|
||||||
|
if ($field = $this->checkFieldsForAction($field->FieldList(), $funcName)) {
|
||||||
|
return $field;
|
||||||
|
}
|
||||||
|
} elseif ($field->hasMethod($funcName) && $field->checkAccessAction($funcName)) {
|
||||||
|
return $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a field request.
|
||||||
|
* Uses {@link Form->dataFieldByName()} to find a matching field,
|
||||||
|
* and falls back to {@link FieldList->fieldByName()} to look
|
||||||
|
* for tabs instead. This means that if you have a tab and a
|
||||||
|
* formfield with the same name, this method gives priority
|
||||||
|
* to the formfield.
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return FormField
|
||||||
|
*/
|
||||||
|
public function handleField($request)
|
||||||
|
{
|
||||||
|
$field = $this->Fields()->dataFieldByName($request->param('FieldName'));
|
||||||
|
|
||||||
|
if ($field) {
|
||||||
|
return $field;
|
||||||
|
} else {
|
||||||
|
// falling back to fieldByName, e.g. for getting tabs
|
||||||
|
return $this->Fields()->fieldByName($request->param('FieldName'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this form into a readonly form
|
||||||
|
*/
|
||||||
|
public function makeReadonly()
|
||||||
|
{
|
||||||
|
$this->transform(new ReadonlyTransformation());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the user should be redirected back down to the
|
||||||
|
* form on the page upon validation errors in the form or if
|
||||||
|
* they just need to redirect back to the page
|
||||||
|
*
|
||||||
|
* @param bool $bool Redirect to form on error?
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setRedirectToFormOnValidationError($bool)
|
||||||
|
{
|
||||||
|
$this->redirectToFormOnValidationError = $bool;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the user should be redirected back down to the
|
||||||
|
* form on the page upon validation errors
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getRedirectToFormOnValidationError()
|
||||||
|
{
|
||||||
|
return $this->redirectToFormOnValidationError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FormTransformation $trans
|
||||||
|
*/
|
||||||
|
public function transform(FormTransformation $trans)
|
||||||
|
{
|
||||||
|
$newFields = new FieldList();
|
||||||
|
foreach ($this->fields as $field) {
|
||||||
|
$newFields->push($field->transform($trans));
|
||||||
|
}
|
||||||
|
$this->fields = $newFields;
|
||||||
|
|
||||||
|
$newActions = new FieldList();
|
||||||
|
foreach ($this->actions as $action) {
|
||||||
|
$newActions->push($action->transform($trans));
|
||||||
|
}
|
||||||
|
$this->actions = $newActions;
|
||||||
|
|
||||||
|
|
||||||
|
// We have to remove validation, if the fields are not editable ;-)
|
||||||
|
if ($this->validator) {
|
||||||
|
$this->validator->removeValidation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link Validator} attached to this form.
|
||||||
|
* @return Validator
|
||||||
|
*/
|
||||||
public function getValidator()
|
public function getValidator()
|
||||||
{
|
{
|
||||||
return $this->validator;
|
return $this->validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the {@link Validator} on this form.
|
* Set the {@link Validator} on this form.
|
||||||
* @param Validator $validator
|
* @param Validator $validator
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setValidator(Validator $validator)
|
public function setValidator(Validator $validator)
|
||||||
{
|
{
|
||||||
if($validator) {
|
if ($validator) {
|
||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
$this->validator->setForm($this);
|
$this->validator->setForm($this);
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the {@link Validator} from this from.
|
* Remove the {@link Validator} from this from.
|
||||||
*/
|
*/
|
||||||
public function unsetValidator()
|
public function unsetValidator()
|
||||||
{
|
{
|
||||||
$this->validator = null;
|
$this->validator = null;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set actions that are exempt from validation
|
* Set actions that are exempt from validation
|
||||||
*
|
*
|
||||||
* @param array
|
* @param array
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setValidationExemptActions($actions)
|
public function setValidationExemptActions($actions)
|
||||||
{
|
{
|
||||||
$this->validationExemptActions = $actions;
|
$this->validationExemptActions = $actions;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of actions that are exempt from validation
|
* Get a list of actions that are exempt from validation
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getValidationExemptActions()
|
public function getValidationExemptActions()
|
||||||
{
|
{
|
||||||
return $this->validationExemptActions;
|
return $this->validationExemptActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Passed a FormAction, returns true if that action is exempt from Form validation
|
* Passed a FormAction, returns true if that action is exempt from Form validation
|
||||||
*
|
*
|
||||||
* @param FormAction $action
|
* @param FormAction $action
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function actionIsValidationExempt($action)
|
public function actionIsValidationExempt($action)
|
||||||
{
|
{
|
||||||
if ($action->getValidationExempt()) {
|
if ($action->getValidationExempt()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (in_array($action->actionName(), $this->getValidationExemptActions())) {
|
if (in_array($action->actionName(), $this->getValidationExemptActions())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate extra special fields - namely the security token field (if required).
|
* Generate extra special fields - namely the security token field (if required).
|
||||||
*
|
*
|
||||||
* @return FieldList
|
* @return FieldList
|
||||||
*/
|
*/
|
||||||
public function getExtraFields()
|
public function getExtraFields()
|
||||||
{
|
{
|
||||||
$extraFields = new FieldList();
|
$extraFields = new FieldList();
|
||||||
|
|
||||||
$token = $this->getSecurityToken();
|
$token = $this->getSecurityToken();
|
||||||
if ($token) {
|
if ($token) {
|
||||||
$tokenField = $token->updateFieldSet($this->fields);
|
$tokenField = $token->updateFieldSet($this->fields);
|
||||||
if ($tokenField) {
|
if ($tokenField) {
|
||||||
$tokenField->setForm($this);
|
$tokenField->setForm($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->securityTokenAdded = true;
|
$this->securityTokenAdded = true;
|
||||||
|
|
||||||
// add the "real" HTTP method if necessary (for PUT, DELETE and HEAD)
|
// add the "real" HTTP method if necessary (for PUT, DELETE and HEAD)
|
||||||
if (strtoupper($this->FormMethod()) != $this->FormHttpMethod()) {
|
if (strtoupper($this->FormMethod()) != $this->FormHttpMethod()) {
|
||||||
$methodField = new HiddenField('_method', '', $this->FormHttpMethod());
|
$methodField = new HiddenField('_method', '', $this->FormHttpMethod());
|
||||||
$methodField->setForm($this);
|
$methodField->setForm($this);
|
||||||
$extraFields->push($methodField);
|
$extraFields->push($methodField);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $extraFields;
|
return $extraFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the form's fields - used by the templates
|
* Return the form's fields - used by the templates
|
||||||
*
|
*
|
||||||
* @return FieldList The form fields
|
* @return FieldList The form fields
|
||||||
*/
|
*/
|
||||||
public function Fields()
|
public function Fields()
|
||||||
{
|
{
|
||||||
foreach($this->getExtraFields() as $field) {
|
foreach ($this->getExtraFields() as $field) {
|
||||||
if (!$this->fields->fieldByName($field->getName())) {
|
if (!$this->fields->fieldByName($field->getName())) {
|
||||||
$this->fields->push($field);
|
$this->fields->push($field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->fields;
|
return $this->fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all <input type="hidden"> fields
|
* Return all <input type="hidden"> fields
|
||||||
* in a form - including fields nested in {@link CompositeFields}.
|
* in a form - including fields nested in {@link CompositeFields}.
|
||||||
* Useful when doing custom field layouts.
|
* Useful when doing custom field layouts.
|
||||||
*
|
*
|
||||||
* @return FieldList
|
* @return FieldList
|
||||||
*/
|
*/
|
||||||
public function HiddenFields()
|
public function HiddenFields()
|
||||||
{
|
{
|
||||||
return $this->Fields()->HiddenFields();
|
return $this->Fields()->HiddenFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all fields except for the hidden fields.
|
* Return all fields except for the hidden fields.
|
||||||
* Useful when making your own simplified form layouts.
|
* Useful when making your own simplified form layouts.
|
||||||
*/
|
*/
|
||||||
public function VisibleFields()
|
public function VisibleFields()
|
||||||
{
|
{
|
||||||
return $this->Fields()->VisibleFields();
|
return $this->Fields()->VisibleFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for the form fields.
|
* Setter for the form fields.
|
||||||
*
|
*
|
||||||
* @param FieldList $fields
|
* @param FieldList $fields
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setFields($fields)
|
public function setFields($fields)
|
||||||
{
|
{
|
||||||
$this->fields = $fields;
|
$this->fields = $fields;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the form's action buttons - used by the templates
|
* Return the form's action buttons - used by the templates
|
||||||
*
|
*
|
||||||
* @return FieldList The action list
|
* @return FieldList The action list
|
||||||
*/
|
*/
|
||||||
public function Actions()
|
public function Actions()
|
||||||
{
|
{
|
||||||
return $this->actions;
|
return $this->actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for the form actions.
|
* Setter for the form actions.
|
||||||
*
|
*
|
||||||
* @param FieldList $actions
|
* @param FieldList $actions
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setActions($actions)
|
public function setActions($actions)
|
||||||
{
|
{
|
||||||
$this->actions = $actions;
|
$this->actions = $actions;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unset all form actions
|
* Unset all form actions
|
||||||
*/
|
*/
|
||||||
public function unsetAllActions()
|
public function unsetAllActions()
|
||||||
{
|
{
|
||||||
$this->actions = new FieldList();
|
$this->actions = new FieldList();
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $value
|
* @param string $value
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setAttribute($name, $value)
|
public function setAttribute($name, $value)
|
||||||
{
|
{
|
||||||
$this->attributes[$name] = $value;
|
$this->attributes[$name] = $value;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getAttribute($name)
|
public function getAttribute($name)
|
||||||
{
|
{
|
||||||
if(isset($this->attributes[$name])) {
|
if (isset($this->attributes[$name])) {
|
||||||
return $this->attributes[$name];
|
return $this->attributes[$name];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getAttributes()
|
public function getAttributes()
|
||||||
{
|
{
|
||||||
$attrs = array(
|
$attrs = array(
|
||||||
'id' => $this->FormName(),
|
'id' => $this->FormName(),
|
||||||
'action' => $this->FormAction(),
|
'action' => $this->FormAction(),
|
||||||
'method' => $this->FormMethod(),
|
'method' => $this->FormMethod(),
|
||||||
'enctype' => $this->getEncType(),
|
'enctype' => $this->getEncType(),
|
||||||
'target' => $this->target,
|
'target' => $this->target,
|
||||||
'class' => $this->extraClass(),
|
'class' => $this->extraClass(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if($this->validator && $this->validator->getErrors()) {
|
if ($this->validator && $this->validator->getErrors()) {
|
||||||
if (!isset($attrs['class'])) {
|
if (!isset($attrs['class'])) {
|
||||||
$attrs['class'] = '';
|
$attrs['class'] = '';
|
||||||
}
|
}
|
||||||
$attrs['class'] .= ' validationerror';
|
$attrs['class'] .= ' validationerror';
|
||||||
}
|
}
|
||||||
|
|
||||||
$attrs = array_merge($attrs, $this->attributes);
|
$attrs = array_merge($attrs, $this->attributes);
|
||||||
|
|
||||||
return $attrs;
|
return $attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the attributes of the form tag - used by the templates.
|
* Return the attributes of the form tag - used by the templates.
|
||||||
*
|
*
|
||||||
* @param array $attrs Custom attributes to process. Falls back to {@link getAttributes()}.
|
* @param array $attrs Custom attributes to process. Falls back to {@link getAttributes()}.
|
||||||
* If at least one argument is passed as a string, all arguments act as excludes by name.
|
* If at least one argument is passed as a string, all arguments act as excludes by name.
|
||||||
*
|
*
|
||||||
* @return string HTML attributes, ready for insertion into an HTML tag
|
* @return string HTML attributes, ready for insertion into an HTML tag
|
||||||
*/
|
*/
|
||||||
public function getAttributesHTML($attrs = null)
|
public function getAttributesHTML($attrs = null)
|
||||||
{
|
{
|
||||||
$exclude = (is_string($attrs)) ? func_get_args() : null;
|
$exclude = (is_string($attrs)) ? func_get_args() : null;
|
||||||
|
|
||||||
// Figure out if we can cache this form
|
// Figure out if we can cache this form
|
||||||
// - forms with validation shouldn't be cached, cos their error messages won't be shown
|
// - forms with validation shouldn't be cached, cos their error messages won't be shown
|
||||||
// - forms with security tokens shouldn't be cached because security tokens expire
|
// - forms with security tokens shouldn't be cached because security tokens expire
|
||||||
$needsCacheDisabled = false;
|
$needsCacheDisabled = false;
|
||||||
if ($this->getSecurityToken()->isEnabled()) {
|
if ($this->getSecurityToken()->isEnabled()) {
|
||||||
$needsCacheDisabled = true;
|
$needsCacheDisabled = true;
|
||||||
}
|
}
|
||||||
if ($this->FormMethod() != 'GET') {
|
if ($this->FormMethod() != 'GET') {
|
||||||
$needsCacheDisabled = true;
|
$needsCacheDisabled = true;
|
||||||
}
|
}
|
||||||
if (!($this->validator instanceof RequiredFields) || count($this->validator->getRequired())) {
|
if (!($this->validator instanceof RequiredFields) || count($this->validator->getRequired())) {
|
||||||
$needsCacheDisabled = true;
|
$needsCacheDisabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we need to disable cache, do it
|
// If we need to disable cache, do it
|
||||||
if ($needsCacheDisabled) {
|
if ($needsCacheDisabled) {
|
||||||
HTTP::set_cache_age(0);
|
HTTP::set_cache_age(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
$attrs = $this->getAttributes();
|
$attrs = $this->getAttributes();
|
||||||
|
|
||||||
// Remove empty
|
// Remove empty
|
||||||
$attrs = array_filter((array)$attrs, create_function('$v', 'return ($v || $v === 0);'));
|
$attrs = array_filter((array)$attrs, create_function('$v', 'return ($v || $v === 0);'));
|
||||||
|
|
||||||
// Remove excluded
|
// Remove excluded
|
||||||
if ($exclude) {
|
if ($exclude) {
|
||||||
$attrs = array_diff_key($attrs, array_flip($exclude));
|
$attrs = array_diff_key($attrs, array_flip($exclude));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare HTML-friendly 'method' attribute (lower-case)
|
// Prepare HTML-friendly 'method' attribute (lower-case)
|
||||||
if (isset($attrs['method'])) {
|
if (isset($attrs['method'])) {
|
||||||
$attrs['method'] = strtolower($attrs['method']);
|
$attrs['method'] = strtolower($attrs['method']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create markup
|
// Create markup
|
||||||
$parts = array();
|
$parts = array();
|
||||||
foreach($attrs as $name => $value) {
|
foreach ($attrs as $name => $value) {
|
||||||
$parts[] = ($value === true) ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\"";
|
$parts[] = ($value === true) ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(' ', $parts);
|
return implode(' ', $parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function FormAttributes()
|
public function FormAttributes()
|
||||||
{
|
{
|
||||||
return $this->getAttributesHTML();
|
return $this->getAttributesHTML();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the target of this form to any value - useful for opening the form contents in a new window or refreshing
|
* Set the target of this form to any value - useful for opening the form contents in a new window or refreshing
|
||||||
* another frame
|
* another frame
|
||||||
*
|
*
|
||||||
* @param string|FormTemplateHelper
|
* @param string|FormTemplateHelper
|
||||||
*/
|
*/
|
||||||
public function setTemplateHelper($helper)
|
public function setTemplateHelper($helper)
|
||||||
{
|
{
|
||||||
$this->templateHelper = $helper;
|
$this->templateHelper = $helper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@link FormTemplateHelper} for this form. If one has not been
|
* Return a {@link FormTemplateHelper} for this form. If one has not been
|
||||||
* set, return the default helper.
|
* set, return the default helper.
|
||||||
*
|
*
|
||||||
* @return FormTemplateHelper
|
* @return FormTemplateHelper
|
||||||
*/
|
*/
|
||||||
public function getTemplateHelper()
|
public function getTemplateHelper()
|
||||||
{
|
{
|
||||||
if($this->templateHelper) {
|
if ($this->templateHelper) {
|
||||||
if(is_string($this->templateHelper)) {
|
if (is_string($this->templateHelper)) {
|
||||||
return Injector::inst()->get($this->templateHelper);
|
return Injector::inst()->get($this->templateHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->templateHelper;
|
return $this->templateHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FormTemplateHelper::singleton();
|
return FormTemplateHelper::singleton();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the target of this form to any value - useful for opening the form
|
* Set the target of this form to any value - useful for opening the form
|
||||||
* contents in a new window or refreshing another frame.
|
* contents in a new window or refreshing another frame.
|
||||||
*
|
*
|
||||||
* @param string $target The value of the target
|
* @param string $target The value of the target
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setTarget($target)
|
public function setTarget($target)
|
||||||
{
|
{
|
||||||
$this->target = $target;
|
$this->target = $target;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the legend value to be inserted into
|
* Set the legend value to be inserted into
|
||||||
* the <legend> element in the Form.ss template.
|
* the <legend> element in the Form.ss template.
|
||||||
* @param string $legend
|
* @param string $legend
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setLegend($legend)
|
public function setLegend($legend)
|
||||||
{
|
{
|
||||||
$this->legend = $legend;
|
$this->legend = $legend;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the SS template that this form should use
|
* Set the SS template that this form should use
|
||||||
* to render with. The default is "Form".
|
* to render with. The default is "Form".
|
||||||
*
|
*
|
||||||
* @param string $template The name of the template (without the .ss extension)
|
* @param string $template The name of the template (without the .ss extension)
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setTemplate($template)
|
public function setTemplate($template)
|
||||||
{
|
{
|
||||||
$this->template = $template;
|
$this->template = $template;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the template to render this form with.
|
* Return the template to render this form with.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTemplate()
|
public function getTemplate()
|
||||||
{
|
{
|
||||||
return $this->template;
|
return $this->template;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returs the ordered list of preferred templates for rendering this form
|
* Returs the ordered list of preferred templates for rendering this form
|
||||||
* If the template isn't set, then default to the
|
* If the template isn't set, then default to the
|
||||||
* form class name e.g "Form".
|
* form class name e.g "Form".
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getTemplates()
|
public function getTemplates()
|
||||||
{
|
{
|
||||||
$templates = SSViewer::get_templates_by_class(get_class($this), '', __CLASS__);
|
$templates = SSViewer::get_templates_by_class(get_class($this), '', __CLASS__);
|
||||||
// Prefer any custom template
|
// Prefer any custom template
|
||||||
if($this->getTemplate()) {
|
if ($this->getTemplate()) {
|
||||||
array_unshift($templates, $this->getTemplate());
|
array_unshift($templates, $this->getTemplate());
|
||||||
}
|
}
|
||||||
return $templates;
|
return $templates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the encoding type for the form.
|
* Returns the encoding type for the form.
|
||||||
*
|
*
|
||||||
* By default this will be URL encoded, unless there is a file field present
|
* By default this will be URL encoded, unless there is a file field present
|
||||||
* in which case multipart is used. You can also set the enc type using
|
* in which case multipart is used. You can also set the enc type using
|
||||||
* {@link setEncType}.
|
* {@link setEncType}.
|
||||||
*/
|
*/
|
||||||
public function getEncType()
|
public function getEncType()
|
||||||
{
|
{
|
||||||
if ($this->encType) {
|
if ($this->encType) {
|
||||||
return $this->encType;
|
return $this->encType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($fields = $this->fields->dataFields()) {
|
if ($fields = $this->fields->dataFields()) {
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
if ($field instanceof FileField) {
|
if ($field instanceof FileField) {
|
||||||
return self::ENC_TYPE_MULTIPART;
|
return self::ENC_TYPE_MULTIPART;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::ENC_TYPE_URLENCODED;
|
return self::ENC_TYPE_URLENCODED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the form encoding type. The most common encoding types are defined
|
* Sets the form encoding type. The most common encoding types are defined
|
||||||
* in {@link ENC_TYPE_URLENCODED} and {@link ENC_TYPE_MULTIPART}.
|
* in {@link ENC_TYPE_URLENCODED} and {@link ENC_TYPE_MULTIPART}.
|
||||||
*
|
*
|
||||||
* @param string $encType
|
* @param string $encType
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setEncType($encType)
|
public function setEncType($encType)
|
||||||
{
|
{
|
||||||
$this->encType = $encType;
|
$this->encType = $encType;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the real HTTP method for the form:
|
* Returns the real HTTP method for the form:
|
||||||
* GET, POST, PUT, DELETE or HEAD.
|
* GET, POST, PUT, DELETE or HEAD.
|
||||||
* As most browsers only support GET and POST in
|
* As most browsers only support GET and POST in
|
||||||
* form submissions, all other HTTP methods are
|
* form submissions, all other HTTP methods are
|
||||||
* added as a hidden field "_method" that
|
* added as a hidden field "_method" that
|
||||||
* gets evaluated in {@link Director::direct()}.
|
* gets evaluated in {@link Director::direct()}.
|
||||||
* See {@link FormMethod()} to get a HTTP method
|
* See {@link FormMethod()} to get a HTTP method
|
||||||
* for safe insertion into a <form> tag.
|
* for safe insertion into a <form> tag.
|
||||||
*
|
*
|
||||||
* @return string HTTP method
|
* @return string HTTP method
|
||||||
*/
|
*/
|
||||||
public function FormHttpMethod()
|
public function FormHttpMethod()
|
||||||
{
|
{
|
||||||
return $this->formMethod;
|
return $this->formMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the form method to be used in the <form> tag.
|
* Returns the form method to be used in the <form> tag.
|
||||||
* See {@link FormHttpMethod()} to get the "real" method.
|
* See {@link FormHttpMethod()} to get the "real" method.
|
||||||
*
|
*
|
||||||
* @return string Form HTTP method restricted to 'GET' or 'POST'
|
* @return string Form HTTP method restricted to 'GET' or 'POST'
|
||||||
*/
|
*/
|
||||||
public function FormMethod()
|
public function FormMethod()
|
||||||
{
|
{
|
||||||
if(in_array($this->formMethod,array('GET','POST'))) {
|
if (in_array($this->formMethod, array('GET','POST'))) {
|
||||||
return $this->formMethod;
|
return $this->formMethod;
|
||||||
} else {
|
} else {
|
||||||
return 'POST';
|
return 'POST';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the form method: GET, POST, PUT, DELETE.
|
* Set the form method: GET, POST, PUT, DELETE.
|
||||||
*
|
*
|
||||||
* @param string $method
|
* @param string $method
|
||||||
* @param bool $strict If non-null, pass value to {@link setStrictFormMethodCheck()}.
|
* @param bool $strict If non-null, pass value to {@link setStrictFormMethodCheck()}.
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setFormMethod($method, $strict = null)
|
public function setFormMethod($method, $strict = null)
|
||||||
{
|
{
|
||||||
$this->formMethod = strtoupper($method);
|
$this->formMethod = strtoupper($method);
|
||||||
if ($strict !== null) {
|
if ($strict !== null) {
|
||||||
$this->setStrictFormMethodCheck($strict);
|
$this->setStrictFormMethodCheck($strict);
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to true, enforce the matching of the form method.
|
* If set to true, enforce the matching of the form method.
|
||||||
*
|
*
|
||||||
* This will mean two things:
|
* This will mean two things:
|
||||||
* - GET vars will be ignored by a POST form, and vice versa
|
* - GET vars will be ignored by a POST form, and vice versa
|
||||||
* - A submission where the HTTP method used doesn't match the form will return a 400 error.
|
* - A submission where the HTTP method used doesn't match the form will return a 400 error.
|
||||||
*
|
*
|
||||||
* If set to false (the default), then the form method is only used to construct the default
|
* If set to false (the default), then the form method is only used to construct the default
|
||||||
* form.
|
* form.
|
||||||
*
|
*
|
||||||
* @param $bool boolean
|
* @param $bool boolean
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setStrictFormMethodCheck($bool)
|
public function setStrictFormMethodCheck($bool)
|
||||||
{
|
{
|
||||||
$this->strictFormMethodCheck = (bool)$bool;
|
$this->strictFormMethodCheck = (bool)$bool;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function getStrictFormMethodCheck()
|
public function getStrictFormMethodCheck()
|
||||||
{
|
{
|
||||||
return $this->strictFormMethodCheck;
|
return $this->strictFormMethodCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the form's action attribute.
|
* Return the form's action attribute.
|
||||||
* This is build by adding an executeForm get variable to the parent controller's Link() value
|
* This is build by adding an executeForm get variable to the parent controller's Link() value
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function FormAction()
|
public function FormAction()
|
||||||
{
|
{
|
||||||
if ($this->formActionPath) {
|
if ($this->formActionPath) {
|
||||||
return $this->formActionPath;
|
return $this->formActionPath;
|
||||||
} elseif($this->controller->hasMethod("FormObjectLink")) {
|
} elseif ($this->controller->hasMethod("FormObjectLink")) {
|
||||||
return $this->controller->FormObjectLink($this->name);
|
return $this->controller->FormObjectLink($this->name);
|
||||||
} else {
|
} else {
|
||||||
return Controller::join_links($this->controller->Link(), $this->name);
|
return Controller::join_links($this->controller->Link(), $this->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the form action attribute to a custom URL.
|
* Set the form action attribute to a custom URL.
|
||||||
*
|
*
|
||||||
* Note: For "normal" forms, you shouldn't need to use this method. It is
|
* Note: For "normal" forms, you shouldn't need to use this method. It is
|
||||||
* recommended only for situations where you have two relatively distinct
|
* recommended only for situations where you have two relatively distinct
|
||||||
* parts of the system trying to communicate via a form post.
|
* parts of the system trying to communicate via a form post.
|
||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setFormAction($path)
|
public function setFormAction($path)
|
||||||
{
|
{
|
||||||
$this->formActionPath = $path;
|
$this->formActionPath = $path;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the form.
|
* Returns the name of the form.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function FormName()
|
public function FormName()
|
||||||
{
|
{
|
||||||
return $this->getTemplateHelper()->generateFormID($this);
|
return $this->getTemplateHelper()->generateFormID($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the HTML ID attribute of the form.
|
* Set the HTML ID attribute of the form.
|
||||||
*
|
*
|
||||||
* @param string $id
|
* @param string $id
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setHTMLID($id)
|
public function setHTMLID($id)
|
||||||
{
|
{
|
||||||
$this->htmlID = $id;
|
$this->htmlID = $id;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getHTMLID()
|
public function getHTMLID()
|
||||||
{
|
{
|
||||||
return $this->htmlID;
|
return $this->htmlID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the controller.
|
* Get the controller.
|
||||||
*
|
*
|
||||||
* @return Controller
|
* @return Controller
|
||||||
*/
|
*/
|
||||||
public function getController()
|
public function getController()
|
||||||
{
|
{
|
||||||
return $this->controller;
|
return $this->controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the controller.
|
* Set the controller.
|
||||||
*
|
*
|
||||||
* @param Controller $controller
|
* @param Controller $controller
|
||||||
* @return Form
|
* @return Form
|
||||||
*/
|
*/
|
||||||
public function setController($controller)
|
public function setController($controller)
|
||||||
{
|
{
|
||||||
$this->controller = $controller;
|
$this->controller = $controller;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the form.
|
* Get the name of the form.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getName()
|
public function getName()
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the name of the form.
|
* Set the name of the form.
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return Form
|
* @return Form
|
||||||
*/
|
*/
|
||||||
public function setName($name)
|
public function setName($name)
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object where there is a method with the same name as each data
|
* Returns an object where there is a method with the same name as each data
|
||||||
* field on the form.
|
* field on the form.
|
||||||
*
|
*
|
||||||
* That method will return the field itself.
|
* That method will return the field itself.
|
||||||
*
|
*
|
||||||
* It means that you can execute $firstName = $form->FieldMap()->FirstName()
|
* It means that you can execute $firstName = $form->FieldMap()->FirstName()
|
||||||
*/
|
*/
|
||||||
public function FieldMap()
|
public function FieldMap()
|
||||||
{
|
{
|
||||||
return new Form_FieldMap($this);
|
return new Form_FieldMap($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The next functions store and modify the forms
|
* Set a message to the session, for display next time this form is shown.
|
||||||
* message attributes. messages are stored in session under
|
*
|
||||||
* $_SESSION[formname][message];
|
* @param string $message the text of the message
|
||||||
*
|
* @param string $type Should be set to good, bad, or warning.
|
||||||
* @return string
|
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||||
*/
|
* Bool values will be treated as plain text flag.
|
||||||
public function Message() {
|
*/
|
||||||
return $this->message;
|
public function sessionMessage($message, $type = ValidationResult::TYPE_ERROR, $cast = ValidationResult::CAST_TEXT)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function MessageType() {
|
|
||||||
return $this->messageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a status message for the form.
|
|
||||||
*
|
|
||||||
* @param string $message the text of the message
|
|
||||||
* @param string $type Should be set to good, bad, or warning.
|
|
||||||
* @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
|
|
||||||
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
|
|
||||||
* user supplied data in the message.
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setMessage($message, $type, $escapeHtml = true)
|
|
||||||
{
|
{
|
||||||
$this->message = ($escapeHtml) ? Convert::raw2xml($message) : $message;
|
$this->setMessage($message, $type, $cast);
|
||||||
$this->messageType = $type;
|
$result = $this->getSessionValidationResult() ?: ValidationResult::create();
|
||||||
return $this;
|
$result->addMessage($message, $type, null, $cast);
|
||||||
}
|
$this->setSessionValidationResult($result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a message to the session, for display next time this form is shown.
|
* Set an error to the session, for display next time this form is shown.
|
||||||
*
|
*
|
||||||
* @param string $message the text of the message
|
* @param string $message the text of the message
|
||||||
* @param string $type Should be set to good, bad, or warning.
|
* @param string $type Should be set to good, bad, or warning.
|
||||||
* @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
|
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||||
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
|
* Bool values will be treated as plain text flag.
|
||||||
* user supplied data in the message.
|
*/
|
||||||
*/
|
public function sessionError($message, $type = ValidationResult::TYPE_ERROR, $cast = ValidationResult::CAST_TEXT)
|
||||||
public function sessionMessage($message, $type, $escapeHtml = true)
|
|
||||||
{
|
{
|
||||||
// Benign message
|
$this->setMessage($message, $type, $cast);
|
||||||
if($type == "good") {
|
$result = $this->getSessionValidationResult() ?: ValidationResult::create();
|
||||||
$this->getSessionValidationResult()->addMessage($message, $type, null, $escapeHtml);
|
$result->addError($message, $type, null, $cast);
|
||||||
|
$this->setSessionValidationResult($result);
|
||||||
|
}
|
||||||
|
|
||||||
// Bad message causing a validation error
|
/**
|
||||||
} else {
|
* Returns the DataObject that has given this form its data
|
||||||
$this->getSessionValidationResult()->addError($message, $type, null, $escapeHtml
|
* through {@link loadDataFrom()}.
|
||||||
);
|
*
|
||||||
}
|
* @return DataObject
|
||||||
}
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated 3.1
|
|
||||||
*/
|
|
||||||
public static function messageForForm($formName, $message, $type) {
|
|
||||||
Deprecation::notice('3.1', 'Create an instance of the form you wish to attach a message to.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ValidationResult stored in the session.
|
|
||||||
* You can use this to modify messages without throwing a ValidationException.
|
|
||||||
* If a ValidationResult doesn't yet exist, a new one will be created
|
|
||||||
*
|
|
||||||
* @return ValidationResult The ValidationResult object stored in the session
|
|
||||||
*/
|
|
||||||
public function getSessionValidationResult() {
|
|
||||||
$result = Session::get("FormInfo.{$this->FormName()}.result");
|
|
||||||
|
|
||||||
if(!$result || !($result instanceof ValidationResult)) {
|
|
||||||
$result = new ValidationResult;
|
|
||||||
Session::set("FormInfo.{$this->FormName()}.result", $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the ValidationResult in the session to be used with the next view of this form.
|
|
||||||
* @param ValidationResult $result The result to save
|
|
||||||
* @param boolean $combineWithExisting If true, then this will be added to the existing result.
|
|
||||||
*/
|
|
||||||
public function setSessionValidationResult(ValidationResult $result, $combineWithExisting = false) {
|
|
||||||
if($combineWithExisting) {
|
|
||||||
$existingResult = $this->getSessionValidationResult();
|
|
||||||
$existingResult->combineAnd($result);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Session::set("FormInfo.{$this->FormName()}.result", $result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clearMessage()
|
|
||||||
{
|
|
||||||
$this->message = null;
|
|
||||||
Session::clear("FormInfo.{$this->FormName()}.result");
|
|
||||||
Session::clear("FormInfo.{$this->FormName()}.data");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetValidation() {
|
|
||||||
Session::clear("FormInfo.{$this->FormName()}.data");
|
|
||||||
Session::clear("FormInfo.{$this->FormName()}.result");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the DataObject that has given this form its data
|
|
||||||
* through {@link loadDataFrom()}.
|
|
||||||
*
|
|
||||||
* @return DataObject
|
|
||||||
*/
|
|
||||||
public function getRecord()
|
public function getRecord()
|
||||||
{
|
{
|
||||||
return $this->record;
|
return $this->record;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the legend value to be inserted into the
|
* Get the legend value to be inserted into the
|
||||||
* <legend> element in Form.ss
|
* <legend> element in Form.ss
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getLegend()
|
public function getLegend()
|
||||||
{
|
{
|
||||||
return $this->legend;
|
return $this->legend;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processing that occurs before a form is executed.
|
* Processing that occurs before a form is executed.
|
||||||
*
|
*
|
||||||
* This includes form validation, if it fails, we throw a ValidationException
|
* This includes form validation, if it fails, we throw a ValidationException
|
||||||
*
|
*
|
||||||
* This includes form validation, if it fails, we redirect back
|
* This includes form validation, if it fails, we redirect back
|
||||||
* to the form with appropriate error messages.
|
* to the form with appropriate error messages.
|
||||||
* Always return true if the current form action is exempt from validation
|
* Always return true if the current form action is exempt from validation
|
||||||
*
|
*
|
||||||
* Triggered through {@link httpSubmission()}.
|
* Triggered through {@link httpSubmission()}.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Note that CSRF protection takes place in {@link httpSubmission()},
|
* Note that CSRF protection takes place in {@link httpSubmission()},
|
||||||
* if it fails the form data will never reach this method.
|
* if it fails the form data will never reach this method.
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return ValidationResult
|
||||||
*/
|
*/
|
||||||
public function validate(){
|
public function validationResult()
|
||||||
$result = $this->validationResult();
|
{
|
||||||
|
// Opportunity to invalidate via validator
|
||||||
|
$action = $this->buttonClicked();
|
||||||
|
if ($action && $this->actionIsValidationExempt($action)) {
|
||||||
|
return ValidationResult::create();
|
||||||
|
}
|
||||||
|
|
||||||
// Valid
|
// Invoke validator
|
||||||
if($result->valid()) {
|
if ($this->validator) {
|
||||||
return true;
|
$result = $this->validator->validate();
|
||||||
|
$this->loadMessagesFrom($result);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
// Invalid
|
// Successful result
|
||||||
} else {
|
return ValidationResult::create();
|
||||||
$this->saveFormErrorsToSession($result, $this->getData());
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const MERGE_DEFAULT = 0;
|
||||||
* Experimental method - return a ValidationResult for the validator
|
const MERGE_CLEAR_MISSING = 1;
|
||||||
* @return [type] [description]
|
const MERGE_IGNORE_FALSEISH = 2;
|
||||||
*/
|
|
||||||
private function validationResult() {
|
|
||||||
// Start with a "valid" validation result
|
|
||||||
$result = ValidationResult::create();
|
|
||||||
|
|
||||||
// Opportunity to invalidate via validator
|
/**
|
||||||
$action = $this->buttonClicked();
|
* Load data from the given DataObject or array.
|
||||||
if($action && $this->actionIsValidationExempt($action)) {
|
*
|
||||||
return $result;
|
* It will call $object->MyField to get the value of MyField.
|
||||||
}
|
* If you passed an array, it will call $object[MyField].
|
||||||
|
* Doesn't save into dataless FormFields ({@link DatalessField}),
|
||||||
if($this->validator){
|
* as determined by {@link FieldList->dataFields()}.
|
||||||
$errors = $this->validator->validate();
|
*
|
||||||
|
* By default, if a field isn't set (as determined by isset()),
|
||||||
// Convert the old-style Validator result into a ValidationResult
|
* its value will not be saved to the field, retaining
|
||||||
if($errors){
|
* potential existing values.
|
||||||
foreach($errors as $error) {
|
*
|
||||||
$result->addFieldError($error['fieldName'], $error['message'], $error['messageType']);
|
* Passed data should not be escaped, and is saved to the FormField instances unescaped.
|
||||||
}
|
* Escaping happens automatically on saving the data through {@link saveInto()}.
|
||||||
}
|
*
|
||||||
}
|
* Escaping happens automatically on saving the data through
|
||||||
|
* {@link saveInto()}.
|
||||||
return $result;
|
*
|
||||||
}
|
* @uses FieldList->dataFields()
|
||||||
|
* @uses FormField->setValue()
|
||||||
const MERGE_DEFAULT = 0;
|
*
|
||||||
const MERGE_CLEAR_MISSING = 1;
|
* @param array|DataObject $data
|
||||||
const MERGE_IGNORE_FALSEISH = 2;
|
* @param int $mergeStrategy
|
||||||
|
* For every field, {@link $data} is interrogated whether it contains a relevant property/key, and
|
||||||
/**
|
* what that property/key's value is.
|
||||||
* Load data from the given DataObject or array.
|
*
|
||||||
*
|
* By default, if {@link $data} does contain a property/key, the fields value is always replaced by {@link $data}'s
|
||||||
* It will call $object->MyField to get the value of MyField.
|
* value, even if that value is null/false/etc. Fields which don't match any property/key in {@link $data} are
|
||||||
* If you passed an array, it will call $object[MyField].
|
* "left alone", meaning they retain any previous value.
|
||||||
* Doesn't save into dataless FormFields ({@link DatalessField}),
|
*
|
||||||
* as determined by {@link FieldList->dataFields()}.
|
* You can pass a bitmask here to change this behaviour.
|
||||||
*
|
*
|
||||||
* By default, if a field isn't set (as determined by isset()),
|
* Passing CLEAR_MISSING means that any fields that don't match any property/key in
|
||||||
* its value will not be saved to the field, retaining
|
* {@link $data} are cleared.
|
||||||
* potential existing values.
|
*
|
||||||
*
|
* Passing IGNORE_FALSEISH means that any false-ish value in {@link $data} won't replace
|
||||||
* Passed data should not be escaped, and is saved to the FormField instances unescaped.
|
* a field's value.
|
||||||
* Escaping happens automatically on saving the data through {@link saveInto()}.
|
*
|
||||||
*
|
* For backwards compatibility reasons, this parameter can also be set to === true, which is the same as passing
|
||||||
* Escaping happens automatically on saving the data through
|
* CLEAR_MISSING
|
||||||
* {@link saveInto()}.
|
*
|
||||||
*
|
* @param array $fieldList An optional list of fields to process. This can be useful when you have a
|
||||||
* @uses FieldList->dataFields()
|
* form that has some fields that save to one object, and some that save to another.
|
||||||
* @uses FormField->setValue()
|
* @return $this
|
||||||
*
|
*/
|
||||||
* @param array|DataObject $data
|
|
||||||
* @param int $mergeStrategy
|
|
||||||
* For every field, {@link $data} is interrogated whether it contains a relevant property/key, and
|
|
||||||
* what that property/key's value is.
|
|
||||||
*
|
|
||||||
* By default, if {@link $data} does contain a property/key, the fields value is always replaced by {@link $data}'s
|
|
||||||
* value, even if that value is null/false/etc. Fields which don't match any property/key in {@link $data} are
|
|
||||||
* "left alone", meaning they retain any previous value.
|
|
||||||
*
|
|
||||||
* You can pass a bitmask here to change this behaviour.
|
|
||||||
*
|
|
||||||
* Passing CLEAR_MISSING means that any fields that don't match any property/key in
|
|
||||||
* {@link $data} are cleared.
|
|
||||||
*
|
|
||||||
* Passing IGNORE_FALSEISH means that any false-ish value in {@link $data} won't replace
|
|
||||||
* a field's value.
|
|
||||||
*
|
|
||||||
* For backwards compatibility reasons, this parameter can also be set to === true, which is the same as passing
|
|
||||||
* CLEAR_MISSING
|
|
||||||
*
|
|
||||||
* @param array $fieldList An optional list of fields to process. This can be useful when you have a
|
|
||||||
* form that has some fields that save to one object, and some that save to another.
|
|
||||||
* @return Form
|
|
||||||
*/
|
|
||||||
public function loadDataFrom($data, $mergeStrategy = 0, $fieldList = null)
|
public function loadDataFrom($data, $mergeStrategy = 0, $fieldList = null)
|
||||||
{
|
{
|
||||||
if(!is_object($data) && !is_array($data)) {
|
if (!is_object($data) && !is_array($data)) {
|
||||||
user_error("Form::loadDataFrom() not passed an array or an object", E_USER_WARNING);
|
user_error("Form::loadDataFrom() not passed an array or an object", E_USER_WARNING);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the backwards compatible case of passing "true" as the second argument
|
// Handle the backwards compatible case of passing "true" as the second argument
|
||||||
if ($mergeStrategy === true) {
|
if ($mergeStrategy === true) {
|
||||||
$mergeStrategy = self::MERGE_CLEAR_MISSING;
|
$mergeStrategy = self::MERGE_CLEAR_MISSING;
|
||||||
} elseif ($mergeStrategy === false) {
|
} elseif ($mergeStrategy === false) {
|
||||||
$mergeStrategy = 0;
|
$mergeStrategy = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if an object is passed, save it for historical reference through {@link getRecord()}
|
// if an object is passed, save it for historical reference through {@link getRecord()}
|
||||||
if (is_object($data)) {
|
if (is_object($data)) {
|
||||||
$this->record = $data;
|
$this->record = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dont include fields without data
|
// dont include fields without data
|
||||||
$dataFields = $this->Fields()->dataFields();
|
$dataFields = $this->Fields()->dataFields();
|
||||||
if ($dataFields) {
|
if (!$dataFields) {
|
||||||
foreach ($dataFields as $field) {
|
return $this;
|
||||||
$name = $field->getName();
|
}
|
||||||
|
|
||||||
// Skip fields that have been excluded
|
/** @var FormField $field */
|
||||||
if($fieldList && !in_array($name, $fieldList)) {
|
foreach ($dataFields as $field) {
|
||||||
continue;
|
$name = $field->getName();
|
||||||
}
|
|
||||||
|
|
||||||
// First check looks for (fieldname)_unchanged, an indicator that we shouldn't overwrite the field value
|
// Skip fields that have been excluded
|
||||||
if (is_array($data) && isset($data[$name . '_unchanged'])) {
|
if ($fieldList && !in_array($name, $fieldList)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check looks for (fieldname)_unchanged, an indicator that we shouldn't overwrite the field value
|
||||||
|
if (is_array($data) && isset($data[$name . '_unchanged'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does this property exist on $data?
|
||||||
|
$exists = false;
|
||||||
|
// The value from $data for this field
|
||||||
|
$val = null;
|
||||||
|
|
||||||
|
if (is_object($data)) {
|
||||||
|
$exists = (
|
||||||
|
isset($data->$name) ||
|
||||||
|
$data->hasMethod($name) ||
|
||||||
|
($data->hasMethod('hasField') && $data->hasField($name))
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
$val = $data->__get($name);
|
||||||
}
|
}
|
||||||
|
} elseif (is_array($data)) {
|
||||||
|
if (array_key_exists($name, $data)) {
|
||||||
|
$exists = true;
|
||||||
|
$val = $data[$name];
|
||||||
|
} // If field is in array-notation we need to access nested data
|
||||||
|
elseif (strpos($name, '[')) {
|
||||||
|
// First encode data using PHP's method of converting nested arrays to form data
|
||||||
|
$flatData = urldecode(http_build_query($data));
|
||||||
|
// Then pull the value out from that flattened string
|
||||||
|
preg_match('/' . addcslashes($name, '[]') . '=([^&]*)/', $flatData, $matches);
|
||||||
|
|
||||||
// Does this property exist on $data?
|
if (isset($matches[1])) {
|
||||||
$exists = false;
|
$exists = true;
|
||||||
// The value from $data for this field
|
$val = $matches[1];
|
||||||
$val = null;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(is_object($data)) {
|
// save to the field if either a value is given, or loading of blank/undefined values is forced
|
||||||
$exists = (
|
if ($exists) {
|
||||||
isset($data->$name) ||
|
if ($val != false || ($mergeStrategy & self::MERGE_IGNORE_FALSEISH) != self::MERGE_IGNORE_FALSEISH) {
|
||||||
$data->hasMethod($name) ||
|
// pass original data as well so composite fields can act on the additional information
|
||||||
($data->hasMethod('hasField') && $data->hasField($name))
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($exists) {
|
|
||||||
$val = $data->__get($name);
|
|
||||||
}
|
|
||||||
} elseif (is_array($data)) {
|
|
||||||
if(array_key_exists($name, $data)) {
|
|
||||||
$exists = true;
|
|
||||||
$val = $data[$name];
|
|
||||||
} // If field is in array-notation we need to access nested data
|
|
||||||
else if(strpos($name,'[')) {
|
|
||||||
// First encode data using PHP's method of converting nested arrays to form data
|
|
||||||
$flatData = urldecode(http_build_query($data));
|
|
||||||
// Then pull the value out from that flattened string
|
|
||||||
preg_match('/' . addcslashes($name,'[]') . '=([^&]*)/', $flatData, $matches);
|
|
||||||
|
|
||||||
if (isset($matches[1])) {
|
|
||||||
$exists = true;
|
|
||||||
$val = $matches[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// save to the field if either a value is given, or loading of blank/undefined values is forced
|
|
||||||
if($exists){
|
|
||||||
if ($val != false || ($mergeStrategy & self::MERGE_IGNORE_FALSEISH) != self::MERGE_IGNORE_FALSEISH){
|
|
||||||
// pass original data as well so composite fields can act on the additional information
|
|
||||||
$field->setValue($val, $data);
|
|
||||||
}
|
|
||||||
} elseif (($mergeStrategy & self::MERGE_CLEAR_MISSING) == self::MERGE_CLEAR_MISSING) {
|
|
||||||
$field->setValue($val, $data);
|
$field->setValue($val, $data);
|
||||||
}
|
}
|
||||||
}
|
} elseif (($mergeStrategy & self::MERGE_CLEAR_MISSING) == self::MERGE_CLEAR_MISSING) {
|
||||||
}
|
$field->setValue($val, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
/**
|
||||||
}
|
* Save the contents of this form into the given data object.
|
||||||
|
* It will make use of setCastedField() to do this.
|
||||||
/**
|
*
|
||||||
* Save the contents of this form into the given data object.
|
* @param DataObjectInterface $dataObject The object to save data into
|
||||||
* It will make use of setCastedField() to do this.
|
* @param FieldList $fieldList An optional list of fields to process. This can be useful when you have a
|
||||||
*
|
* form that has some fields that save to one object, and some that save to another.
|
||||||
* @param DataObjectInterface $dataObject The object to save data into
|
*/
|
||||||
* @param FieldList $fieldList An optional list of fields to process. This can be useful when you have a
|
|
||||||
* form that has some fields that save to one object, and some that save to another.
|
|
||||||
*/
|
|
||||||
public function saveInto(DataObjectInterface $dataObject, $fieldList = null)
|
public function saveInto(DataObjectInterface $dataObject, $fieldList = null)
|
||||||
{
|
{
|
||||||
$dataFields = $this->fields->saveableFields();
|
$dataFields = $this->fields->saveableFields();
|
||||||
$lastField = null;
|
$lastField = null;
|
||||||
if ($dataFields) {
|
if ($dataFields) {
|
||||||
foreach ($dataFields as $field) {
|
foreach ($dataFields as $field) {
|
||||||
// Skip fields that have been excluded
|
// Skip fields that have been excluded
|
||||||
if ($fieldList && is_array($fieldList) && !in_array($field->getName(), $fieldList)) {
|
if ($fieldList && is_array($fieldList) && !in_array($field->getName(), $fieldList)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$saveMethod = "save{$field->getName()}";
|
||||||
$saveMethod = "save{$field->getName()}";
|
if ($field->getName() == "ClassName") {
|
||||||
|
$lastField = $field;
|
||||||
if($field->getName() == "ClassName"){
|
} elseif ($dataObject->hasMethod($saveMethod)) {
|
||||||
$lastField = $field;
|
$dataObject->$saveMethod($field->dataValue());
|
||||||
}else if( $dataObject->hasMethod( $saveMethod ) ){
|
} elseif ($field->getName() !== "ID") {
|
||||||
$dataObject->$saveMethod( $field->dataValue());
|
$field->saveInto($dataObject);
|
||||||
} else if($field->getName() != "ID"){
|
}
|
||||||
$field->saveInto($dataObject);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ($lastField) {
|
if ($lastField) {
|
||||||
$lastField->saveInto($dataObject);
|
$lastField->saveInto($dataObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the submitted data from this form through
|
* Get the submitted data from this form through
|
||||||
* {@link FieldList->dataFields()}, which filters out
|
* {@link FieldList->dataFields()}, which filters out
|
||||||
* any form-specific data like form-actions.
|
* any form-specific data like form-actions.
|
||||||
* Calls {@link FormField->dataValue()} on each field,
|
* Calls {@link FormField->dataValue()} on each field,
|
||||||
* which returns a value suitable for insertion into a DataObject
|
* which returns a value suitable for insertion into a DataObject
|
||||||
* property.
|
* property.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getData()
|
public function getData()
|
||||||
{
|
{
|
||||||
$dataFields = $this->fields->dataFields();
|
$dataFields = $this->fields->dataFields();
|
||||||
$data = array();
|
$data = array();
|
||||||
|
|
||||||
if($dataFields){
|
if ($dataFields) {
|
||||||
foreach($dataFields as $field) {
|
foreach ($dataFields as $field) {
|
||||||
if($field->getName()) {
|
if ($field->getName()) {
|
||||||
$data[$field->getName()] = $field->dataValue();
|
$data[$field->getName()] = $field->dataValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a rendered version of this form.
|
* Return a rendered version of this form.
|
||||||
*
|
*
|
||||||
* This is returned when you access a form as $FormObject rather
|
* This is returned when you access a form as $FormObject rather
|
||||||
* than <% with FormObject %>
|
* than <% with FormObject %>
|
||||||
*
|
*
|
||||||
* @return DBHTMLText
|
* @return DBHTMLText
|
||||||
*/
|
*/
|
||||||
public function forTemplate()
|
public function forTemplate()
|
||||||
{
|
{
|
||||||
$return = $this->renderWith($this->getTemplates());
|
$return = $this->renderWith($this->getTemplates());
|
||||||
|
|
||||||
// Now that we're rendered, clear message
|
// Now that we're rendered, clear message
|
||||||
$this->clearMessage();
|
$this->clearMessage();
|
||||||
|
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a rendered version of this form, suitable for ajax post-back.
|
* Return a rendered version of this form, suitable for ajax post-back.
|
||||||
*
|
*
|
||||||
* It triggers slightly different behaviour, such as disabling the rewriting
|
* It triggers slightly different behaviour, such as disabling the rewriting
|
||||||
* of # links.
|
* of # links.
|
||||||
*
|
*
|
||||||
* @return DBHTMLText
|
* @return DBHTMLText
|
||||||
*/
|
*/
|
||||||
public function forAjaxTemplate()
|
public function forAjaxTemplate()
|
||||||
{
|
{
|
||||||
$view = new SSViewer($this->getTemplates());
|
$view = new SSViewer($this->getTemplates());
|
||||||
|
|
||||||
$return = $view->dontRewriteHashlinks()->process($this);
|
$return = $view->dontRewriteHashlinks()->process($this);
|
||||||
|
|
||||||
// Now that we're rendered, clear message
|
// Now that we're rendered, clear message
|
||||||
$this->clearMessage();
|
$this->clearMessage();
|
||||||
|
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an HTML rendition of this form, without the <form> tag itself.
|
* Returns an HTML rendition of this form, without the <form> tag itself.
|
||||||
*
|
*
|
||||||
* Attaches 3 extra hidden files, _form_action, _form_name, _form_method,
|
* Attaches 3 extra hidden files, _form_action, _form_name, _form_method,
|
||||||
* and _form_enctype. These are the attributes of the form. These fields
|
* and _form_enctype. These are the attributes of the form. These fields
|
||||||
* can be used to send the form to Ajax.
|
* can be used to send the form to Ajax.
|
||||||
*
|
*
|
||||||
* @deprecated 5.0
|
* @deprecated 5.0
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function formHtmlContent()
|
public function formHtmlContent()
|
||||||
{
|
{
|
||||||
Deprecation::notice('5.0');
|
Deprecation::notice('5.0');
|
||||||
$this->IncludeFormTag = false;
|
$this->IncludeFormTag = false;
|
||||||
$content = $this->forTemplate();
|
$content = $this->forTemplate();
|
||||||
$this->IncludeFormTag = true;
|
$this->IncludeFormTag = true;
|
||||||
|
|
||||||
$content .= "<input type=\"hidden\" name=\"_form_action\" id=\"" . $this->FormName . "_form_action\""
|
$content .= "<input type=\"hidden\" name=\"_form_action\" id=\"" . $this->FormName . "_form_action\""
|
||||||
. " value=\"" . $this->FormAction() . "\" />\n";
|
. " value=\"" . $this->FormAction() . "\" />\n";
|
||||||
$content .= "<input type=\"hidden\" name=\"_form_name\" value=\"" . $this->FormName() . "\" />\n";
|
$content .= "<input type=\"hidden\" name=\"_form_name\" value=\"" . $this->FormName() . "\" />\n";
|
||||||
$content .= "<input type=\"hidden\" name=\"_form_method\" value=\"" . $this->FormMethod() . "\" />\n";
|
$content .= "<input type=\"hidden\" name=\"_form_method\" value=\"" . $this->FormMethod() . "\" />\n";
|
||||||
$content .= "<input type=\"hidden\" name=\"_form_enctype\" value=\"" . $this->getEncType() . "\" />\n";
|
$content .= "<input type=\"hidden\" name=\"_form_enctype\" value=\"" . $this->getEncType() . "\" />\n";
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render this form using the given template, and return the result as a string
|
* Render this form using the given template, and return the result as a string
|
||||||
* You can pass either an SSViewer or a template name
|
* You can pass either an SSViewer or a template name
|
||||||
* @param string|array $template
|
* @param string|array $template
|
||||||
* @return DBHTMLText
|
* @return DBHTMLText
|
||||||
*/
|
*/
|
||||||
public function renderWithoutActionButton($template)
|
public function renderWithoutActionButton($template)
|
||||||
{
|
{
|
||||||
$custom = $this->customise(array(
|
$custom = $this->customise(array(
|
||||||
"Actions" => "",
|
"Actions" => "",
|
||||||
));
|
));
|
||||||
|
|
||||||
if(is_string($template)) {
|
if (is_string($template)) {
|
||||||
$template = new SSViewer($template);
|
$template = new SSViewer($template);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $template->process($custom);
|
return $template->process($custom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the button that was clicked. This should only be called by the Controller.
|
* Sets the button that was clicked. This should only be called by the Controller.
|
||||||
*
|
*
|
||||||
* @param callable $funcName The name of the action method that will be called.
|
* @param callable $funcName The name of the action method that will be called.
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setButtonClicked($funcName)
|
public function setButtonClicked($funcName)
|
||||||
{
|
{
|
||||||
$this->buttonClickedFunc = $funcName;
|
$this->buttonClickedFunc = $funcName;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FormAction
|
* @return FormAction
|
||||||
*/
|
*/
|
||||||
public function buttonClicked()
|
public function buttonClicked()
|
||||||
{
|
{
|
||||||
$actions = $this->getAllActions();
|
$actions = $this->getAllActions();
|
||||||
foreach ($actions as $action) {
|
foreach ($actions as $action) {
|
||||||
if ($this->buttonClickedFunc === $action->actionName()) {
|
if ($this->buttonClickedFunc === $action->actionName()) {
|
||||||
return $action;
|
return $action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of all actions, including those in the main "fields" FieldList
|
* Get a list of all actions, including those in the main "fields" FieldList
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getAllActions()
|
protected function getAllActions()
|
||||||
{
|
{
|
||||||
$fields = $this->fields->dataFields() ?: array();
|
$fields = $this->fields->dataFields() ?: array();
|
||||||
$actions = $this->actions->dataFields() ?: array();
|
$actions = $this->actions->dataFields() ?: array();
|
||||||
|
|
||||||
$fieldsAndActions = array_merge($fields, $actions);
|
$fieldsAndActions = array_merge($fields, $actions);
|
||||||
$actions = array_filter($fieldsAndActions, function($fieldOrAction) {
|
$actions = array_filter($fieldsAndActions, function ($fieldOrAction) {
|
||||||
return $fieldOrAction instanceof FormAction;
|
return $fieldOrAction instanceof FormAction;
|
||||||
});
|
});
|
||||||
|
|
||||||
return $actions;
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the default button that should be clicked when another one isn't
|
* Return the default button that should be clicked when another one isn't
|
||||||
* available.
|
* available.
|
||||||
*
|
*
|
||||||
* @return FormAction
|
* @return FormAction
|
||||||
*/
|
*/
|
||||||
public function defaultAction()
|
public function defaultAction()
|
||||||
{
|
{
|
||||||
if($this->hasDefaultAction && $this->actions) {
|
if ($this->hasDefaultAction && $this->actions) {
|
||||||
return $this->actions->first();
|
return $this->actions->first();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable the default button.
|
* Disable the default button.
|
||||||
*
|
*
|
||||||
* Ordinarily, when a form is processed and no action_XXX button is
|
* Ordinarily, when a form is processed and no action_XXX button is
|
||||||
* available, then the first button in the actions list will be pressed.
|
* available, then the first button in the actions list will be pressed.
|
||||||
* However, if this is "delete", for example, this isn't such a good idea.
|
* However, if this is "delete", for example, this isn't such a good idea.
|
||||||
*
|
*
|
||||||
* @return Form
|
* @return Form
|
||||||
*/
|
*/
|
||||||
public function disableDefaultAction()
|
public function disableDefaultAction()
|
||||||
{
|
{
|
||||||
$this->hasDefaultAction = false;
|
$this->hasDefaultAction = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable the requirement of a security token on this form instance. This
|
* Disable the requirement of a security token on this form instance. This
|
||||||
* security protects against CSRF attacks, but you should disable this if
|
* security protects against CSRF attacks, but you should disable this if
|
||||||
* you don't want to tie a form to a session - eg a search form.
|
* you don't want to tie a form to a session - eg a search form.
|
||||||
*
|
*
|
||||||
* Check for token state with {@link getSecurityToken()} and
|
* Check for token state with {@link getSecurityToken()} and
|
||||||
* {@link SecurityToken->isEnabled()}.
|
* {@link SecurityToken->isEnabled()}.
|
||||||
*
|
*
|
||||||
* @return Form
|
* @return Form
|
||||||
*/
|
*/
|
||||||
public function disableSecurityToken()
|
public function disableSecurityToken()
|
||||||
{
|
{
|
||||||
$this->securityToken = new NullSecurityToken();
|
$this->securityToken = new NullSecurityToken();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable {@link SecurityToken} protection for this form instance.
|
* Enable {@link SecurityToken} protection for this form instance.
|
||||||
*
|
*
|
||||||
* Check for token state with {@link getSecurityToken()} and
|
* Check for token state with {@link getSecurityToken()} and
|
||||||
* {@link SecurityToken->isEnabled()}.
|
* {@link SecurityToken->isEnabled()}.
|
||||||
*
|
*
|
||||||
* @return Form
|
* @return Form
|
||||||
*/
|
*/
|
||||||
public function enableSecurityToken()
|
public function enableSecurityToken()
|
||||||
{
|
{
|
||||||
$this->securityToken = new SecurityToken();
|
$this->securityToken = new SecurityToken();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the security token for this form (if any exists).
|
* Returns the security token for this form (if any exists).
|
||||||
*
|
*
|
||||||
* Doesn't check for {@link securityTokenEnabled()}.
|
* Doesn't check for {@link securityTokenEnabled()}.
|
||||||
*
|
*
|
||||||
* Use {@link SecurityToken::inst()} to get a global token.
|
* Use {@link SecurityToken::inst()} to get a global token.
|
||||||
*
|
*
|
||||||
* @return SecurityToken|null
|
* @return SecurityToken|null
|
||||||
*/
|
*/
|
||||||
public function getSecurityToken()
|
public function getSecurityToken()
|
||||||
{
|
{
|
||||||
return $this->securityToken;
|
return $this->securityToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles all CSS-classes.
|
* Compiles all CSS-classes.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function extraClass()
|
public function extraClass()
|
||||||
{
|
{
|
||||||
return implode(array_unique($this->extraClasses), ' ');
|
return implode(array_unique($this->extraClasses), ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a CSS-class to the form-container. If needed, multiple classes can
|
* Add a CSS-class to the form-container. If needed, multiple classes can
|
||||||
* be added by delimiting a string with spaces.
|
* be added by delimiting a string with spaces.
|
||||||
*
|
*
|
||||||
* @param string $class A string containing a classname or several class
|
* @param string $class A string containing a classname or several class
|
||||||
* names delimited by a single space.
|
* names delimited by a single space.
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function addExtraClass($class)
|
public function addExtraClass($class)
|
||||||
{
|
{
|
||||||
//split at white space
|
//split at white space
|
||||||
$classes = preg_split('/\s+/', $class);
|
$classes = preg_split('/\s+/', $class);
|
||||||
foreach($classes as $class) {
|
foreach ($classes as $class) {
|
||||||
//add classes one by one
|
//add classes one by one
|
||||||
$this->extraClasses[$class] = $class;
|
$this->extraClasses[$class] = $class;
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a CSS-class from the form-container. Multiple class names can
|
* Remove a CSS-class from the form-container. Multiple class names can
|
||||||
* be passed through as a space delimited string
|
* be passed through as a space delimited string
|
||||||
*
|
*
|
||||||
* @param string $class
|
* @param string $class
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function removeExtraClass($class)
|
public function removeExtraClass($class)
|
||||||
{
|
{
|
||||||
//split at white space
|
//split at white space
|
||||||
$classes = preg_split('/\s+/', $class);
|
$classes = preg_split('/\s+/', $class);
|
||||||
foreach ($classes as $class) {
|
foreach ($classes as $class) {
|
||||||
//unset one by one
|
//unset one by one
|
||||||
unset($this->extraClasses[$class]);
|
unset($this->extraClasses[$class]);
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function debug()
|
public function debug()
|
||||||
{
|
{
|
||||||
$result = "<h3>$this->class</h3><ul>";
|
$result = "<h3>$this->class</h3><ul>";
|
||||||
foreach($this->fields as $field) {
|
foreach ($this->fields as $field) {
|
||||||
$result .= "<li>$field" . $field->debug() . "</li>";
|
$result .= "<li>$field" . $field->debug() . "</li>";
|
||||||
}
|
}
|
||||||
$result .= "</ul>";
|
$result .= "</ul>";
|
||||||
|
|
||||||
if( $this->validator ) {
|
if ($this->validator) {
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
$result .= '<h3>' . _t('Form.VALIDATOR', 'Validator') . '</h3>' . $this->validator->debug();
|
$result .= '<h3>'._t('Form.VALIDATOR', 'Validator').'</h3>' . $this->validator->debug();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// TESTING HELPERS
|
// TESTING HELPERS
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test a submission of this form.
|
* Test a submission of this form.
|
||||||
* @param string $action
|
* @param string $action
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @return HTTPResponse the response object that the handling controller produces. You can interrogate this in
|
* @return HTTPResponse the response object that the handling controller produces. You can interrogate this in
|
||||||
* your unit test.
|
* your unit test.
|
||||||
* @throws HTTPResponse_Exception
|
* @throws HTTPResponse_Exception
|
||||||
*/
|
*/
|
||||||
public function testSubmission($action, $data)
|
public function testSubmission($action, $data)
|
||||||
{
|
{
|
||||||
$data['action_' . $action] = true;
|
$data['action_' . $action] = true;
|
||||||
|
|
||||||
return Director::test($this->FormAction(), $data, Controller::curr()->getSession());
|
return Director::test($this->FormAction(), $data, Controller::curr()->getSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test an ajax submission of this form.
|
* Test an ajax submission of this form.
|
||||||
*
|
*
|
||||||
* @param string $action
|
* @param string $action
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @return HTTPResponse the response object that the handling controller produces. You can interrogate this in
|
* @return HTTPResponse the response object that the handling controller produces. You can interrogate this in
|
||||||
* your unit test.
|
* your unit test.
|
||||||
*/
|
*/
|
||||||
public function testAjaxSubmission($action, $data)
|
public function testAjaxSubmission($action, $data)
|
||||||
{
|
{
|
||||||
$data['ajax'] = 1;
|
$data['ajax'] = 1;
|
||||||
return $this->testSubmission($action, $data);
|
return $this->testSubmission($action, $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ use SilverStripe\View\SSViewer;
|
|||||||
*/
|
*/
|
||||||
class FormField extends RequestHandler
|
class FormField extends RequestHandler
|
||||||
{
|
{
|
||||||
|
use FormMessage;
|
||||||
|
|
||||||
/** @see $schemaDataType */
|
/** @see $schemaDataType */
|
||||||
const SCHEMA_DATA_TYPE_STRING = 'String';
|
const SCHEMA_DATA_TYPE_STRING = 'String';
|
||||||
@ -103,16 +104,6 @@ class FormField extends RequestHandler
|
|||||||
*/
|
*/
|
||||||
protected $value;
|
protected $value;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $messageType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@ -274,8 +265,6 @@ class FormField extends RequestHandler
|
|||||||
'HolderID' => 'Text',
|
'HolderID' => 'Text',
|
||||||
'Title' => 'Text',
|
'Title' => 'Text',
|
||||||
'RightTitle' => 'Text',
|
'RightTitle' => 'Text',
|
||||||
'MessageType' => 'Text',
|
|
||||||
'Message' => 'HTMLFragment',
|
|
||||||
'Description' => 'HTMLFragment',
|
'Description' => 'HTMLFragment',
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -456,32 +445,6 @@ class FormField extends RequestHandler
|
|||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the field message, used by form validation.
|
|
||||||
*
|
|
||||||
* Use {@link setError()} to set this property.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function Message()
|
|
||||||
{
|
|
||||||
return $this->message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the field message type.
|
|
||||||
*
|
|
||||||
* Arbitrary value which is mostly used for CSS classes in the rendered HTML, e.g "required".
|
|
||||||
*
|
|
||||||
* Use {@link setError()} to set this property.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function MessageType()
|
|
||||||
{
|
|
||||||
return $this->messageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the field value.
|
* Returns the field value.
|
||||||
*
|
*
|
||||||
@ -613,8 +576,8 @@ class FormField extends RequestHandler
|
|||||||
// e.g. red borders on input tags.
|
// e.g. red borders on input tags.
|
||||||
//
|
//
|
||||||
// CSS class needs to be different from the one rendered through {@link FieldHolder()}.
|
// CSS class needs to be different from the one rendered through {@link FieldHolder()}.
|
||||||
if ($this->Message()) {
|
if ($this->getMessage()) {
|
||||||
$classes[] .= 'holder-' . $this->MessageType();
|
$classes[] .= 'holder-' . $this->getMessageType();
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(' ', $classes);
|
return implode(' ', $classes);
|
||||||
@ -871,22 +834,13 @@ class FormField extends RequestHandler
|
|||||||
return $form->getSecurityToken()->isEnabled();
|
return $form->getSecurityToken()->isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function castingHelper($field)
|
||||||
* Sets the error message to be displayed on the form field.
|
|
||||||
*
|
|
||||||
* Allows HTML content, so remember to use Convert::raw2xml().
|
|
||||||
*
|
|
||||||
* @param string $message
|
|
||||||
* @param string $messageType
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setError($message, $messageType)
|
|
||||||
{
|
{
|
||||||
$this->message = $message;
|
// Override casting for field message
|
||||||
$this->messageType = $messageType;
|
if (strcasecmp($field, 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
|
||||||
|
return $helper;
|
||||||
return $this;
|
}
|
||||||
|
return parent::castingHelper($field);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1211,12 +1165,12 @@ class FormField extends RequestHandler
|
|||||||
*/
|
*/
|
||||||
public function performReadonlyTransformation()
|
public function performReadonlyTransformation()
|
||||||
{
|
{
|
||||||
$readonlyClassName = $this->class . '_Readonly';
|
$readonlyClassName = static::class . '_Readonly';
|
||||||
|
|
||||||
if (ClassInfo::exists($readonlyClassName)) {
|
if (ClassInfo::exists($readonlyClassName)) {
|
||||||
$clone = $this->castedCopy($readonlyClassName);
|
$clone = $this->castedCopy($readonlyClassName);
|
||||||
} else {
|
} else {
|
||||||
$clone = $this->castedCopy('SilverStripe\\Forms\\ReadonlyField');
|
$clone = $this->castedCopy(ReadonlyField::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
$clone->setReadonly(true);
|
$clone->setReadonly(true);
|
||||||
@ -1606,17 +1560,10 @@ class FormField extends RequestHandler
|
|||||||
'name' => $this->getName(),
|
'name' => $this->getName(),
|
||||||
'id' => $this->ID(),
|
'id' => $this->ID(),
|
||||||
'value' => $this->Value(),
|
'value' => $this->Value(),
|
||||||
'message' => null,
|
'message' => $this->getSchemaMessage(),
|
||||||
'data' => [],
|
'data' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($message = $this->Message()) {
|
|
||||||
$state['message'] = [
|
|
||||||
'value' => ['html' => $message],
|
|
||||||
'type' => $this->MessageType(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
132
src/Forms/FormMessage.php
Normal file
132
src/Forms/FormMessage.php
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Forms;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
use SilverStripe\View\ViewableData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form component which contains a castable message
|
||||||
|
*
|
||||||
|
* @mixin ViewableData
|
||||||
|
*/
|
||||||
|
trait FormMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $message = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $messageType = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casting for message
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $messageCast = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the field message, used by form validation.
|
||||||
|
*
|
||||||
|
* Use {@link setError()} to set this property.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMessage()
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the field message type.
|
||||||
|
*
|
||||||
|
* Arbitrary value which is mostly used for CSS classes in the rendered HTML, e.g "required".
|
||||||
|
*
|
||||||
|
* Use {@link setError()} to set this property.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMessageType()
|
||||||
|
{
|
||||||
|
return $this->messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casting type for this message. Will be 'text' or 'html'
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMessageCast()
|
||||||
|
{
|
||||||
|
return $this->messageCast;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the error message to be displayed on the form field.
|
||||||
|
*
|
||||||
|
* Allows HTML content, so remember to use Convert::raw2xml().
|
||||||
|
*
|
||||||
|
* @param string $message Message string
|
||||||
|
* @param string $messageType Message type
|
||||||
|
* @param string $messageCast
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMessage(
|
||||||
|
$message,
|
||||||
|
$messageType = ValidationResult::TYPE_ERROR,
|
||||||
|
$messageCast = ValidationResult::CAST_TEXT
|
||||||
|
) {
|
||||||
|
if (!in_array($messageCast, [ValidationResult::CAST_TEXT, ValidationResult::CAST_HTML])) {
|
||||||
|
throw new InvalidArgumentException("Invalid message cast type");
|
||||||
|
}
|
||||||
|
$this->message = $message;
|
||||||
|
$this->messageType = $messageType;
|
||||||
|
$this->messageCast = $messageCast;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get casting helper for message cast, or null if not known
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getMessageCastingHelper()
|
||||||
|
{
|
||||||
|
switch ($this->getMessageCast()) {
|
||||||
|
case ValidationResult::CAST_TEXT:
|
||||||
|
return 'Text';
|
||||||
|
case ValidationResult::CAST_HTML:
|
||||||
|
return 'HTMLFragment';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get form schema encoded message
|
||||||
|
*
|
||||||
|
* @return array|null Message in array format, or null if no message
|
||||||
|
*/
|
||||||
|
public function getSchemaMessage()
|
||||||
|
{
|
||||||
|
$message = $this->getMessage();
|
||||||
|
if (!$message) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Form schema messages treat simple strings as plain text, so nest for html messages
|
||||||
|
if ($this->getMessageCast() === ValidationResult::CAST_HTML) {
|
||||||
|
$message = ['html' => $message];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'value' => $message,
|
||||||
|
'type' => $this->getMessageType(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -24,101 +24,101 @@ use SilverStripe\ORM\ValidationException;
|
|||||||
class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_ActionProvider
|
class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_ActionProvider
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is set to true, this {@link GridField_ActionProvider} will
|
* If this is set to true, this {@link GridField_ActionProvider} will
|
||||||
* remove the object from the list, instead of deleting.
|
* remove the object from the list, instead of deleting.
|
||||||
*
|
*
|
||||||
* In the case of a has one, has many or many many list it will uncouple
|
* In the case of a has one, has many or many many list it will uncouple
|
||||||
* the item from the list.
|
* the item from the list.
|
||||||
*
|
*
|
||||||
* @var boolean
|
* @var boolean
|
||||||
*/
|
*/
|
||||||
protected $removeRelation = false;
|
protected $removeRelation = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param boolean $removeRelation - true if removing the item from the list, but not deleting it
|
* @param boolean $removeRelation - true if removing the item from the list, but not deleting it
|
||||||
*/
|
*/
|
||||||
public function __construct($removeRelation = false)
|
public function __construct($removeRelation = false)
|
||||||
{
|
{
|
||||||
$this->removeRelation = $removeRelation;
|
$this->removeRelation = $removeRelation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a column 'Delete'
|
* Add a column 'Delete'
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @param array $columns
|
* @param array $columns
|
||||||
*/
|
*/
|
||||||
public function augmentColumns($gridField, &$columns)
|
public function augmentColumns($gridField, &$columns)
|
||||||
{
|
{
|
||||||
if(!in_array('Actions', $columns)) {
|
if (!in_array('Actions', $columns)) {
|
||||||
$columns[] = 'Actions';
|
$columns[] = 'Actions';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return any special attributes that will be used for FormField::create_tag()
|
* Return any special attributes that will be used for FormField::create_tag()
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @param DataObject $record
|
* @param DataObject $record
|
||||||
* @param string $columnName
|
* @param string $columnName
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getColumnAttributes($gridField, $record, $columnName)
|
public function getColumnAttributes($gridField, $record, $columnName)
|
||||||
{
|
{
|
||||||
return array('class' => 'grid-field__col-compact');
|
return array('class' => 'grid-field__col-compact');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the title
|
* Add the title
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @param string $columnName
|
* @param string $columnName
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getColumnMetadata($gridField, $columnName)
|
public function getColumnMetadata($gridField, $columnName)
|
||||||
{
|
{
|
||||||
if($columnName == 'Actions') {
|
if ($columnName == 'Actions') {
|
||||||
return array('title' => '');
|
return array('title' => '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which columns are handled by this component
|
* Which columns are handled by this component
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getColumnsHandled($gridField)
|
public function getColumnsHandled($gridField)
|
||||||
{
|
{
|
||||||
return array('Actions');
|
return array('Actions');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which GridField actions are this component handling
|
* Which GridField actions are this component handling
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getActions($gridField)
|
public function getActions($gridField)
|
||||||
{
|
{
|
||||||
return array('deleterecord', 'unlinkrelation');
|
return array('deleterecord', 'unlinkrelation');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @param DataObject $record
|
* @param DataObject $record
|
||||||
* @param string $columnName
|
* @param string $columnName
|
||||||
* @return string the HTML for the column
|
* @return string the HTML for the column
|
||||||
*/
|
*/
|
||||||
public function getColumnContent($gridField, $record, $columnName)
|
public function getColumnContent($gridField, $record, $columnName)
|
||||||
{
|
{
|
||||||
if($this->removeRelation) {
|
if ($this->removeRelation) {
|
||||||
if(!$record->canEdit()) {
|
if (!$record->canEdit()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$field = GridField_FormAction::create(
|
$field = GridField_FormAction::create(
|
||||||
$gridField,
|
$gridField,
|
||||||
@ -127,12 +127,12 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
|
|||||||
"unlinkrelation",
|
"unlinkrelation",
|
||||||
array('RecordID' => $record->ID)
|
array('RecordID' => $record->ID)
|
||||||
)
|
)
|
||||||
->addExtraClass('btn btn--no-text btn--icon-md font-icon-link-broken grid-field__icon-action gridfield-button-unlink')
|
->addExtraClass('btn btn--no-text btn--icon-md font-icon-link-broken grid-field__icon-action gridfield-button-unlink')
|
||||||
->setAttribute('title', _t('GridAction.UnlinkRelation', "Unlink"));
|
->setAttribute('title', _t('GridAction.UnlinkRelation', "Unlink"));
|
||||||
} else {
|
} else {
|
||||||
if(!$record->canDelete()) {
|
if (!$record->canDelete()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$field = GridField_FormAction::create(
|
$field = GridField_FormAction::create(
|
||||||
$gridField,
|
$gridField,
|
||||||
@ -141,46 +141,48 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
|
|||||||
"deleterecord",
|
"deleterecord",
|
||||||
array('RecordID' => $record->ID)
|
array('RecordID' => $record->ID)
|
||||||
)
|
)
|
||||||
->addExtraClass('gridfield-button-delete btn--icon-md font-icon-trash-bin btn--no-text grid-field__icon-action')
|
->addExtraClass('gridfield-button-delete btn--icon-md font-icon-trash-bin btn--no-text grid-field__icon-action')
|
||||||
->setAttribute('title', _t('GridAction.Delete', "Delete"))
|
->setAttribute('title', _t('GridAction.Delete', "Delete"))
|
||||||
->setDescription(_t('GridAction.DELETE_DESCRIPTION','Delete'));
|
->setDescription(_t('GridAction.DELETE_DESCRIPTION', 'Delete'));
|
||||||
}
|
}
|
||||||
return $field->Field();
|
return $field->Field();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the actions and apply any changes to the GridField
|
* Handle the actions and apply any changes to the GridField
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @param string $actionName
|
* @param string $actionName
|
||||||
* @param mixed $arguments
|
* @param mixed $arguments
|
||||||
* @param array $data - form data
|
* @param array $data - form data
|
||||||
* @throws ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
public function handleAction(GridField $gridField, $actionName, $arguments, $data)
|
public function handleAction(GridField $gridField, $actionName, $arguments, $data)
|
||||||
{
|
{
|
||||||
if($actionName == 'deleterecord' || $actionName == 'unlinkrelation') {
|
if ($actionName == 'deleterecord' || $actionName == 'unlinkrelation') {
|
||||||
/** @var DataObject $item */
|
/** @var DataObject $item */
|
||||||
$item = $gridField->getList()->byID($arguments['RecordID']);
|
$item = $gridField->getList()->byID($arguments['RecordID']);
|
||||||
if(!$item) {
|
if (!$item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($actionName == 'deleterecord') {
|
if ($actionName == 'deleterecord') {
|
||||||
if(!$item->canDelete()) {
|
if (!$item->canDelete()) {
|
||||||
throw new ValidationException(
|
throw new ValidationException(
|
||||||
_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"));
|
_t('GridFieldAction_Delete.DeletePermissionsFailure', "No delete permissions")
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$item->delete();
|
$item->delete();
|
||||||
} else {
|
} else {
|
||||||
if(!$item->canEdit()) {
|
if (!$item->canEdit()) {
|
||||||
throw new ValidationException(
|
throw new ValidationException(
|
||||||
_t('GridFieldAction_Delete.EditPermissionsFailure',"No permission to unlink record"));
|
_t('GridFieldAction_Delete.EditPermissionsFailure', "No permission to unlink record")
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$gridField->getList()->remove($item);
|
$gridField->getList()->remove($item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,9 @@ namespace SilverStripe\Forms\GridField;
|
|||||||
|
|
||||||
use SilverStripe\Admin\LeftAndMain;
|
use SilverStripe\Admin\LeftAndMain;
|
||||||
use SilverStripe\Control\Controller;
|
use SilverStripe\Control\Controller;
|
||||||
use SilverStripe\Control\PjaxResponseNegotiator;
|
|
||||||
use SilverStripe\Control\RequestHandler;
|
use SilverStripe\Control\RequestHandler;
|
||||||
use SilverStripe\Control\HTTPRequest;
|
use SilverStripe\Control\HTTPRequest;
|
||||||
use SilverStripe\Control\HTTPResponse;
|
use SilverStripe\Control\HTTPResponse;
|
||||||
use SilverStripe\Control\HTTPResponse_Exception;
|
|
||||||
use SilverStripe\Forms\FieldList;
|
use SilverStripe\Forms\FieldList;
|
||||||
use SilverStripe\Forms\Form;
|
use SilverStripe\Forms\Form;
|
||||||
use SilverStripe\Forms\FormAction;
|
use SilverStripe\Forms\FormAction;
|
||||||
@ -20,626 +18,592 @@ use SilverStripe\ORM\HasManyList;
|
|||||||
use SilverStripe\ORM\ManyManyList;
|
use SilverStripe\ORM\ManyManyList;
|
||||||
use SilverStripe\ORM\SS_List;
|
use SilverStripe\ORM\SS_List;
|
||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationException;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\View\ArrayData;
|
use SilverStripe\View\ArrayData;
|
||||||
use SilverStripe\View\SSViewer;
|
use SilverStripe\View\SSViewer;
|
||||||
|
|
||||||
class GridFieldDetailForm_ItemRequest extends RequestHandler
|
class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
private static $allowed_actions = array(
|
||||||
'edit',
|
'edit',
|
||||||
'view',
|
'view',
|
||||||
'ItemEditForm'
|
'ItemEditForm'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @var GridField
|
* @var GridField
|
||||||
*/
|
*/
|
||||||
protected $gridField;
|
protected $gridField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @var GridFieldDetailForm
|
* @var GridFieldDetailForm
|
||||||
*/
|
*/
|
||||||
protected $component;
|
protected $component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @var DataObject
|
* @var DataObject
|
||||||
*/
|
*/
|
||||||
protected $record;
|
protected $record;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This represents the current parent RequestHandler (which does not necessarily need to be a Controller).
|
* This represents the current parent RequestHandler (which does not necessarily need to be a Controller).
|
||||||
* It allows us to traverse the RequestHandler chain upwards to reach the Controller stack.
|
* It allows us to traverse the RequestHandler chain upwards to reach the Controller stack.
|
||||||
*
|
*
|
||||||
* @var RequestHandler
|
* @var RequestHandler
|
||||||
*/
|
*/
|
||||||
protected $popupController;
|
protected $popupController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $popupFormName;
|
protected $popupFormName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var String
|
* @var String
|
||||||
*/
|
*/
|
||||||
protected $template = null;
|
protected $template = null;
|
||||||
|
|
||||||
private static $url_handlers = array(
|
private static $url_handlers = array(
|
||||||
'$Action!' => '$Action',
|
'$Action!' => '$Action',
|
||||||
'' => 'edit',
|
'' => 'edit',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param GridField $gridField
|
* @param GridField $gridField
|
||||||
* @param GridFieldDetailForm $component
|
* @param GridFieldDetailForm $component
|
||||||
* @param DataObject $record
|
* @param DataObject $record
|
||||||
* @param RequestHandler $requestHandler
|
* @param RequestHandler $requestHandler
|
||||||
* @param string $popupFormName
|
* @param string $popupFormName
|
||||||
*/
|
*/
|
||||||
public function __construct($gridField, $component, $record, $requestHandler, $popupFormName)
|
public function __construct($gridField, $component, $record, $requestHandler, $popupFormName)
|
||||||
{
|
{
|
||||||
$this->gridField = $gridField;
|
$this->gridField = $gridField;
|
||||||
$this->component = $component;
|
$this->component = $component;
|
||||||
$this->record = $record;
|
$this->record = $record;
|
||||||
$this->popupController = $requestHandler;
|
$this->popupController = $requestHandler;
|
||||||
$this->popupFormName = $popupFormName;
|
$this->popupFormName = $popupFormName;
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Link($action = null)
|
public function Link($action = null)
|
||||||
{
|
{
|
||||||
return Controller::join_links(
|
return Controller::join_links(
|
||||||
$this->gridField->Link('item'),
|
$this->gridField->Link('item'),
|
||||||
$this->record->ID ? $this->record->ID : 'new',
|
$this->record->ID ? $this->record->ID : 'new',
|
||||||
$action
|
$action
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param HTTPRequest $request
|
* @param HTTPRequest $request
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function view($request)
|
public function view($request)
|
||||||
{
|
{
|
||||||
if (!$this->record->canView()) {
|
if (!$this->record->canView()) {
|
||||||
$this->httpError(403);
|
$this->httpError(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$controller = $this->getToplevelController();
|
$controller = $this->getToplevelController();
|
||||||
|
|
||||||
$form = $this->ItemEditForm();
|
$form = $this->ItemEditForm();
|
||||||
$form->makeReadonly();
|
$form->makeReadonly();
|
||||||
|
|
||||||
$data = new ArrayData(array(
|
$data = new ArrayData(array(
|
||||||
'Backlink' => $controller->Link(),
|
'Backlink' => $controller->Link(),
|
||||||
'ItemEditForm' => $form
|
'ItemEditForm' => $form
|
||||||
));
|
));
|
||||||
$return = $data->renderWith($this->getTemplates());
|
$return = $data->renderWith($this->getTemplates());
|
||||||
|
|
||||||
if ($request->isAjax()) {
|
if ($request->isAjax()) {
|
||||||
return $return;
|
return $return;
|
||||||
} else {
|
} else {
|
||||||
return $controller->customise(array('Content' => $return));
|
return $controller->customise(array('Content' => $return));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param HTTPRequest $request
|
* @param HTTPRequest $request
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function edit($request)
|
public function edit($request)
|
||||||
{
|
{
|
||||||
$controller = $this->getToplevelController();
|
$controller = $this->getToplevelController();
|
||||||
$form = $this->ItemEditForm();
|
$form = $this->ItemEditForm();
|
||||||
|
|
||||||
$return = $this->customise(array(
|
$return = $this->customise(array(
|
||||||
'Backlink' => $controller->hasMethod('Backlink') ? $controller->Backlink() : $controller->Link(),
|
'Backlink' => $controller->hasMethod('Backlink') ? $controller->Backlink() : $controller->Link(),
|
||||||
'ItemEditForm' => $form,
|
'ItemEditForm' => $form,
|
||||||
))->renderWith($this->getTemplates());
|
))->renderWith($this->getTemplates());
|
||||||
|
|
||||||
if ($request->isAjax()) {
|
if ($request->isAjax()) {
|
||||||
return $return;
|
return $return;
|
||||||
} else {
|
} else {
|
||||||
// If not requested by ajax, we need to render it within the controller context+template
|
// If not requested by ajax, we need to render it within the controller context+template
|
||||||
return $controller->customise(array(
|
return $controller->customise(array(
|
||||||
// TODO CMS coupling
|
// TODO CMS coupling
|
||||||
'Content' => $return,
|
'Content' => $return,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
|
* Builds an item edit form. The arguments to getCMSFields() are the popupController and
|
||||||
* popupFormName, however this is an experimental API and may change.
|
* popupFormName, however this is an experimental API and may change.
|
||||||
*
|
*
|
||||||
* @todo In the future, we will probably need to come up with a tigher object representing a partially
|
* @todo In the future, we will probably need to come up with a tigher object representing a partially
|
||||||
* complete controller with gaps for extra functionality. This, for example, would be a better way
|
* complete controller with gaps for extra functionality. This, for example, would be a better way
|
||||||
* of letting Security/login put its log-in form inside a UI specified elsewhere.
|
* of letting Security/login put its log-in form inside a UI specified elsewhere.
|
||||||
*
|
*
|
||||||
* @return Form
|
* @return Form
|
||||||
*/
|
*/
|
||||||
public function ItemEditForm()
|
public function ItemEditForm()
|
||||||
{
|
{
|
||||||
$list = $this->gridField->getList();
|
$list = $this->gridField->getList();
|
||||||
|
|
||||||
if (empty($this->record)) {
|
if (empty($this->record)) {
|
||||||
$controller = $this->getToplevelController();
|
$controller = $this->getToplevelController();
|
||||||
$url = $controller->getRequest()->getURL();
|
$url = $controller->getRequest()->getURL();
|
||||||
$noActionURL = $controller->removeAction($url);
|
$noActionURL = $controller->removeAction($url);
|
||||||
$controller->getResponse()->removeHeader('Location'); //clear the existing redirect
|
$controller->getResponse()->removeHeader('Location'); //clear the existing redirect
|
||||||
return $controller->redirect($noActionURL, 302);
|
return $controller->redirect($noActionURL, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
$canView = $this->record->canView();
|
$canView = $this->record->canView();
|
||||||
$canEdit = $this->record->canEdit();
|
$canEdit = $this->record->canEdit();
|
||||||
$canDelete = $this->record->canDelete();
|
$canDelete = $this->record->canDelete();
|
||||||
$canCreate = $this->record->canCreate();
|
$canCreate = $this->record->canCreate();
|
||||||
|
|
||||||
if (!$canView) {
|
if (!$canView) {
|
||||||
$controller = $this->getToplevelController();
|
$controller = $this->getToplevelController();
|
||||||
// TODO More friendly error
|
// TODO More friendly error
|
||||||
return $controller->httpError(403);
|
return $controller->httpError(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build actions
|
// Build actions
|
||||||
$actions = $this->getFormActions();
|
$actions = $this->getFormActions();
|
||||||
|
|
||||||
// If we are creating a new record in a has-many list, then
|
// If we are creating a new record in a has-many list, then
|
||||||
// pre-populate the record's foreign key.
|
// pre-populate the record's foreign key.
|
||||||
if ($list instanceof HasManyList && !$this->record->isInDB()) {
|
if ($list instanceof HasManyList && !$this->record->isInDB()) {
|
||||||
$key = $list->getForeignKey();
|
$key = $list->getForeignKey();
|
||||||
$id = $list->getForeignID();
|
$id = $list->getForeignID();
|
||||||
$this->record->$key = $id;
|
$this->record->$key = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fields = $this->component->getFields();
|
$fields = $this->component->getFields();
|
||||||
if (!$fields) {
|
if (!$fields) {
|
||||||
$fields = $this->record->getCMSFields();
|
$fields = $this->record->getCMSFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are creating a new record in a has-many list, then
|
// If we are creating a new record in a has-many list, then
|
||||||
// Disable the form field as it has no effect.
|
// Disable the form field as it has no effect.
|
||||||
if ($list instanceof HasManyList) {
|
if ($list instanceof HasManyList) {
|
||||||
$key = $list->getForeignKey();
|
$key = $list->getForeignKey();
|
||||||
|
|
||||||
if ($field = $fields->dataFieldByName($key)) {
|
if ($field = $fields->dataFieldByName($key)) {
|
||||||
$fields->makeFieldReadonly($field);
|
$fields->makeFieldReadonly($field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Caution: API violation. Form expects a Controller, but we are giving it a RequestHandler instead.
|
// Caution: API violation. Form expects a Controller, but we are giving it a RequestHandler instead.
|
||||||
// Thanks to this however, we are able to nest GridFields, and also access the initial Controller by
|
// Thanks to this however, we are able to nest GridFields, and also access the initial Controller by
|
||||||
// dereferencing GridFieldDetailForm_ItemRequest->getController() multiple times. See getToplevelController
|
// dereferencing GridFieldDetailForm_ItemRequest->getController() multiple times. See getToplevelController
|
||||||
// below.
|
// below.
|
||||||
$form = new Form(
|
$form = new Form(
|
||||||
$this,
|
$this,
|
||||||
'ItemEditForm',
|
'ItemEditForm',
|
||||||
$fields,
|
$fields,
|
||||||
$actions,
|
$actions,
|
||||||
$this->component->getValidator()
|
$this->component->getValidator()
|
||||||
);
|
);
|
||||||
|
|
||||||
$form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT);
|
$form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT);
|
||||||
|
|
||||||
if ($this->record->ID && !$canEdit) {
|
if ($this->record->ID && !$canEdit) {
|
||||||
// Restrict editing of existing records
|
// Restrict editing of existing records
|
||||||
$form->makeReadonly();
|
$form->makeReadonly();
|
||||||
// Hack to re-enable delete button if user can delete
|
// Hack to re-enable delete button if user can delete
|
||||||
if ($canDelete) {
|
if ($canDelete) {
|
||||||
$form->Actions()->fieldByName('action_doDelete')->setReadonly(false);
|
$form->Actions()->fieldByName('action_doDelete')->setReadonly(false);
|
||||||
}
|
}
|
||||||
} elseif (!$this->record->ID && !$canCreate) {
|
} elseif (!$this->record->ID && !$canCreate) {
|
||||||
// Restrict creation of new records
|
// Restrict creation of new records
|
||||||
$form->makeReadonly();
|
$form->makeReadonly();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load many_many extraData for record.
|
// Load many_many extraData for record.
|
||||||
// Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
|
// Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
|
||||||
if ($list instanceof ManyManyList) {
|
if ($list instanceof ManyManyList) {
|
||||||
$extraData = $list->getExtraData('', $this->record->ID);
|
$extraData = $list->getExtraData('', $this->record->ID);
|
||||||
$form->loadDataFrom(array('ManyMany' => $extraData));
|
$form->loadDataFrom(array('ManyMany' => $extraData));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Coupling with CMS
|
// TODO Coupling with CMS
|
||||||
$toplevelController = $this->getToplevelController();
|
$toplevelController = $this->getToplevelController();
|
||||||
if ($toplevelController && $toplevelController instanceof LeftAndMain) {
|
if ($toplevelController && $toplevelController instanceof LeftAndMain) {
|
||||||
// Always show with base template (full width, no other panels),
|
// Always show with base template (full width, no other panels),
|
||||||
// regardless of overloaded CMS controller templates.
|
// regardless of overloaded CMS controller templates.
|
||||||
// TODO Allow customization, e.g. to display an edit form alongside a search form from the CMS controller
|
// TODO Allow customization, e.g. to display an edit form alongside a search form from the CMS controller
|
||||||
$form->setTemplate([
|
$form->setTemplate([
|
||||||
'type' => 'Includes',
|
'type' => 'Includes',
|
||||||
'SilverStripe\\Admin\\LeftAndMain_EditForm',
|
'SilverStripe\\Admin\\LeftAndMain_EditForm',
|
||||||
]);
|
]);
|
||||||
$form->addExtraClass('cms-content cms-edit-form center fill-height flexbox-area-grow');
|
$form->addExtraClass('cms-content cms-edit-form center fill-height flexbox-area-grow');
|
||||||
$form->setAttribute('data-pjax-fragment', 'CurrentForm Content');
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm Content');
|
||||||
if ($form->Fields()->hasTabSet()) {
|
if ($form->Fields()->hasTabSet()) {
|
||||||
$form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet');
|
$form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet');
|
||||||
$form->addExtraClass('cms-tabset');
|
$form->addExtraClass('cms-tabset');
|
||||||
}
|
}
|
||||||
|
|
||||||
$form->Backlink = $this->getBackLink();
|
$form->Backlink = $this->getBackLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
$cb = $this->component->getItemEditFormCallback();
|
$cb = $this->component->getItemEditFormCallback();
|
||||||
if ($cb) {
|
if ($cb) {
|
||||||
$cb($form, $this);
|
$cb($form, $this);
|
||||||
}
|
}
|
||||||
$this->extend("updateItemEditForm", $form);
|
$this->extend("updateItemEditForm", $form);
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the set of form field actions for this DataObject
|
* Build the set of form field actions for this DataObject
|
||||||
*
|
*
|
||||||
* @return FieldList
|
* @return FieldList
|
||||||
*/
|
*/
|
||||||
protected function getFormActions()
|
protected function getFormActions()
|
||||||
{
|
{
|
||||||
$canEdit = $this->record->canEdit();
|
$canEdit = $this->record->canEdit();
|
||||||
$canDelete = $this->record->canDelete();
|
$canDelete = $this->record->canDelete();
|
||||||
$actions = new FieldList();
|
$actions = new FieldList();
|
||||||
if ($this->record->ID !== 0) {
|
if ($this->record->ID !== 0) {
|
||||||
if ($canEdit) {
|
if ($canEdit) {
|
||||||
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
|
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
|
||||||
->setUseButtonTag(true)
|
->setUseButtonTag(true)
|
||||||
->addExtraClass('ss-ui-action-constructive')
|
->addExtraClass('ss-ui-action-constructive')
|
||||||
->setAttribute('data-icon', 'accept'));
|
->setAttribute('data-icon', 'accept'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($canDelete) {
|
if ($canDelete) {
|
||||||
$actions->push(FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
|
$actions->push(FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
|
||||||
->setUseButtonTag(true)
|
->setUseButtonTag(true)
|
||||||
->addExtraClass('ss-ui-action-destructive action-delete'));
|
->addExtraClass('ss-ui-action-destructive action-delete'));
|
||||||
}
|
}
|
||||||
} else { // adding new record
|
} else { // adding new record
|
||||||
//Change the Save label to 'Create'
|
//Change the Save label to 'Create'
|
||||||
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Create', 'Create'))
|
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Create', 'Create'))
|
||||||
->setUseButtonTag(true)
|
->setUseButtonTag(true)
|
||||||
->addExtraClass('ss-ui-action-constructive')
|
->addExtraClass('ss-ui-action-constructive')
|
||||||
->setAttribute('data-icon', 'add'));
|
->setAttribute('data-icon', 'add'));
|
||||||
|
|
||||||
// Add a Cancel link which is a button-like link and link back to one level up.
|
// Add a Cancel link which is a button-like link and link back to one level up.
|
||||||
$crumbs = $this->Breadcrumbs();
|
$crumbs = $this->Breadcrumbs();
|
||||||
if ($crumbs && $crumbs->count() >= 2) {
|
if ($crumbs && $crumbs->count() >= 2) {
|
||||||
$oneLevelUp = $crumbs->offsetGet($crumbs->count() - 2);
|
$oneLevelUp = $crumbs->offsetGet($crumbs->count() - 2);
|
||||||
$text = sprintf(
|
$text = sprintf(
|
||||||
"<a class=\"%s\" href=\"%s\">%s</a>",
|
"<a class=\"%s\" href=\"%s\">%s</a>",
|
||||||
"crumb ss-ui-button ss-ui-action-destructive cms-panel-link ui-corner-all", // CSS classes
|
"crumb ss-ui-button ss-ui-action-destructive cms-panel-link ui-corner-all", // CSS classes
|
||||||
$oneLevelUp->Link, // url
|
$oneLevelUp->Link, // url
|
||||||
_t('GridFieldDetailForm.CancelBtn', 'Cancel') // label
|
_t('GridFieldDetailForm.CancelBtn', 'Cancel') // label
|
||||||
);
|
);
|
||||||
$actions->push(new LiteralField('cancelbutton', $text));
|
$actions->push(new LiteralField('cancelbutton', $text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->extend('updateFormActions', $actions);
|
$this->extend('updateFormActions', $actions);
|
||||||
return $actions;
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traverse the nested RequestHandlers until we reach something that's not GridFieldDetailForm_ItemRequest.
|
* Traverse the nested RequestHandlers until we reach something that's not GridFieldDetailForm_ItemRequest.
|
||||||
* This allows us to access the Controller responsible for invoking the top-level GridField.
|
* This allows us to access the Controller responsible for invoking the top-level GridField.
|
||||||
* This should be equivalent to getting the controller off the top of the controller stack via Controller::curr(),
|
* This should be equivalent to getting the controller off the top of the controller stack via Controller::curr(),
|
||||||
* but allows us to avoid accessing the global state.
|
* but allows us to avoid accessing the global state.
|
||||||
*
|
*
|
||||||
* GridFieldDetailForm_ItemRequests are RequestHandlers, and as such they are not part of the controller stack.
|
* GridFieldDetailForm_ItemRequests are RequestHandlers, and as such they are not part of the controller stack.
|
||||||
*
|
*
|
||||||
* @return Controller
|
* @return Controller
|
||||||
*/
|
*/
|
||||||
protected function getToplevelController()
|
protected function getToplevelController()
|
||||||
{
|
{
|
||||||
$c = $this->popupController;
|
$c = $this->popupController;
|
||||||
while ($c && $c instanceof GridFieldDetailForm_ItemRequest) {
|
while ($c && $c instanceof GridFieldDetailForm_ItemRequest) {
|
||||||
$c = $c->getController();
|
$c = $c->getController();
|
||||||
}
|
}
|
||||||
return $c;
|
return $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getBackLink()
|
protected function getBackLink()
|
||||||
{
|
{
|
||||||
// TODO Coupling with CMS
|
// TODO Coupling with CMS
|
||||||
$backlink = '';
|
$backlink = '';
|
||||||
$toplevelController = $this->getToplevelController();
|
$toplevelController = $this->getToplevelController();
|
||||||
if ($toplevelController && $toplevelController instanceof LeftAndMain) {
|
if ($toplevelController && $toplevelController instanceof LeftAndMain) {
|
||||||
if ($toplevelController->hasMethod('Backlink')) {
|
if ($toplevelController->hasMethod('Backlink')) {
|
||||||
$backlink = $toplevelController->Backlink();
|
$backlink = $toplevelController->Backlink();
|
||||||
} elseif ($this->popupController->hasMethod('Breadcrumbs')) {
|
} elseif ($this->popupController->hasMethod('Breadcrumbs')) {
|
||||||
$parents = $this->popupController->Breadcrumbs(false)->items;
|
$parents = $this->popupController->Breadcrumbs(false)->items;
|
||||||
$backlink = array_pop($parents)->Link;
|
$backlink = array_pop($parents)->Link;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!$backlink) {
|
if (!$backlink) {
|
||||||
$backlink = $toplevelController->Link();
|
$backlink = $toplevelController->Link();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $backlink;
|
return $backlink;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of extra data from the $record as saved into it by
|
* Get the list of extra data from the $record as saved into it by
|
||||||
* {@see Form::saveInto()}
|
* {@see Form::saveInto()}
|
||||||
*
|
*
|
||||||
* Handles detection of falsey values explicitly saved into the
|
* Handles detection of falsey values explicitly saved into the
|
||||||
* DataObject by formfields
|
* DataObject by formfields
|
||||||
*
|
*
|
||||||
* @param DataObject $record
|
* @param DataObject $record
|
||||||
* @param SS_List $list
|
* @param SS_List $list
|
||||||
* @return array List of data to write to the relation
|
* @return array List of data to write to the relation
|
||||||
*/
|
*/
|
||||||
protected function getExtraSavedData($record, $list)
|
protected function getExtraSavedData($record, $list)
|
||||||
{
|
{
|
||||||
// Skip extra data if not ManyManyList
|
// Skip extra data if not ManyManyList
|
||||||
if (!($list instanceof ManyManyList)) {
|
if (!($list instanceof ManyManyList)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = array();
|
$data = array();
|
||||||
foreach ($list->getExtraFields() as $field => $dbSpec) {
|
foreach ($list->getExtraFields() as $field => $dbSpec) {
|
||||||
$savedField = "ManyMany[{$field}]";
|
$savedField = "ManyMany[{$field}]";
|
||||||
if ($record->hasField($savedField)) {
|
if ($record->hasField($savedField)) {
|
||||||
$data[$field] = $record->getField($savedField);
|
$data[$field] = $record->getField($savedField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function doSave($data, $form)
|
public function doSave($data, $form)
|
||||||
{
|
{
|
||||||
$isNewRecord = $this->record->ID == 0;
|
$isNewRecord = $this->record->ID == 0;
|
||||||
|
|
||||||
// Check permission
|
// Check permission
|
||||||
if (!$this->record->canEdit()) {
|
if (!$this->record->canEdit()) {
|
||||||
return $this->httpError(403);
|
return $this->httpError(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save from form data
|
// Save from form data
|
||||||
try {
|
$this->saveFormIntoRecord($data, $form);
|
||||||
$this->saveFormIntoRecord($data, $form);
|
|
||||||
} catch (ValidationException $e) {
|
|
||||||
return $this->generateValidationResponse($form, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$link = '<a href="' . $this->Link('edit') . '">"'
|
$link = '<a href="' . $this->Link('edit') . '">"'
|
||||||
. htmlspecialchars($this->record->Title, ENT_QUOTES)
|
. htmlspecialchars($this->record->Title, ENT_QUOTES)
|
||||||
. '"</a>';
|
. '"</a>';
|
||||||
$message = _t(
|
$message = _t(
|
||||||
'GridFieldDetailForm.Saved',
|
'GridFieldDetailForm.Saved',
|
||||||
'Saved {name} {link}',
|
'Saved {name} {link}',
|
||||||
array(
|
array(
|
||||||
'name' => $this->record->i18n_singular_name(),
|
'name' => $this->record->i18n_singular_name(),
|
||||||
'link' => $link
|
'link' => $link
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$form->sessionMessage($message, 'good', false);
|
$form->sessionMessage($message, 'good', ValidationResult::CAST_HTML);
|
||||||
|
|
||||||
// Redirect after save
|
// Redirect after save
|
||||||
return $this->redirectAfterSave($isNewRecord);
|
return $this->redirectAfterSave($isNewRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response object for this request after a successful save
|
* Response object for this request after a successful save
|
||||||
*
|
*
|
||||||
* @param bool $isNewRecord True if this record was just created
|
* @param bool $isNewRecord True if this record was just created
|
||||||
* @return HTTPResponse|DBHTMLText
|
* @return HTTPResponse|DBHTMLText
|
||||||
*/
|
*/
|
||||||
protected function redirectAfterSave($isNewRecord)
|
protected function redirectAfterSave($isNewRecord)
|
||||||
{
|
{
|
||||||
$controller = $this->getToplevelController();
|
$controller = $this->getToplevelController();
|
||||||
if ($isNewRecord) {
|
if ($isNewRecord) {
|
||||||
return $controller->redirect($this->Link());
|
return $controller->redirect($this->Link());
|
||||||
} elseif ($this->gridField->getList()->byID($this->record->ID)) {
|
} elseif ($this->gridField->getList()->byID($this->record->ID)) {
|
||||||
// Return new view, as we can't do a "virtual redirect" via the CMS Ajax
|
// Return new view, as we can't do a "virtual redirect" via the CMS Ajax
|
||||||
// to the same URL (it assumes that its content is already current, and doesn't reload)
|
// to the same URL (it assumes that its content is already current, and doesn't reload)
|
||||||
return $this->edit($controller->getRequest());
|
return $this->edit($controller->getRequest());
|
||||||
} else {
|
} else {
|
||||||
// Changes to the record properties might've excluded the record from
|
// Changes to the record properties might've excluded the record from
|
||||||
// a filtered list, so return back to the main view if it can't be found
|
// a filtered list, so return back to the main view if it can't be found
|
||||||
$url = $controller->getRequest()->getURL();
|
$url = $controller->getRequest()->getURL();
|
||||||
$noActionURL = $controller->removeAction($url);
|
$noActionURL = $controller->removeAction($url);
|
||||||
$controller->getRequest()->addHeader('X-Pjax', 'Content');
|
$controller->getRequest()->addHeader('X-Pjax', 'Content');
|
||||||
return $controller->redirect($noActionURL, 302);
|
return $controller->redirect($noActionURL, 302);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function httpError($errorCode, $errorMessage = null)
|
public function httpError($errorCode, $errorMessage = null)
|
||||||
{
|
{
|
||||||
$controller = $this->getToplevelController();
|
$controller = $this->getToplevelController();
|
||||||
return $controller->httpError($errorCode, $errorMessage);
|
return $controller->httpError($errorCode, $errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the given form data into the underlying dataobject and relation
|
* Loads the given form data into the underlying dataobject and relation
|
||||||
*
|
*
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @param Form $form
|
* @param Form $form
|
||||||
* @throws ValidationException On error
|
* @throws ValidationException On error
|
||||||
* @return DataObject Saved record
|
* @return DataObject Saved record
|
||||||
*/
|
*/
|
||||||
protected function saveFormIntoRecord($data, $form)
|
protected function saveFormIntoRecord($data, $form)
|
||||||
{
|
{
|
||||||
$list = $this->gridField->getList();
|
$list = $this->gridField->getList();
|
||||||
|
|
||||||
// Check object matches the correct classname
|
// Check object matches the correct classname
|
||||||
if (isset($data['ClassName']) && $data['ClassName'] != $this->record->ClassName) {
|
if (isset($data['ClassName']) && $data['ClassName'] != $this->record->ClassName) {
|
||||||
$newClassName = $data['ClassName'];
|
$newClassName = $data['ClassName'];
|
||||||
// The records originally saved attribute was overwritten by $form->saveInto($record) before.
|
// The records originally saved attribute was overwritten by $form->saveInto($record) before.
|
||||||
// This is necessary for newClassInstance() to work as expected, and trigger change detection
|
// This is necessary for newClassInstance() to work as expected, and trigger change detection
|
||||||
// on the ClassName attribute
|
// on the ClassName attribute
|
||||||
$this->record->setClassName($this->record->ClassName);
|
$this->record->setClassName($this->record->ClassName);
|
||||||
// Replace $record with a new instance
|
// Replace $record with a new instance
|
||||||
$this->record = $this->record->newClassInstance($newClassName);
|
$this->record = $this->record->newClassInstance($newClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save form and any extra saved data into this dataobject
|
// Save form and any extra saved data into this dataobject
|
||||||
$form->saveInto($this->record);
|
$form->saveInto($this->record);
|
||||||
$this->record->write();
|
$this->record->write();
|
||||||
$extraData = $this->getExtraSavedData($this->record, $list);
|
$extraData = $this->getExtraSavedData($this->record, $list);
|
||||||
$list->add($this->record, $extraData);
|
$list->add($this->record, $extraData);
|
||||||
|
|
||||||
return $this->record;
|
return $this->record;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a response object for a form validation error
|
* @param array $data
|
||||||
*
|
* @param Form $form
|
||||||
* @param Form $form The source form
|
* @return HTTPResponse
|
||||||
* @param ValidationException $e The validation error message
|
* @throws ValidationException
|
||||||
* @return HTTPResponse
|
*/
|
||||||
* @throws HTTPResponse_Exception
|
public function doDelete($data, $form)
|
||||||
*/
|
{
|
||||||
protected function generateValidationResponse($form, $e)
|
$title = $this->record->Title;
|
||||||
{
|
if (!$this->record->canDelete()) {
|
||||||
$controller = $this->getToplevelController();
|
throw new ValidationException(
|
||||||
|
_t('GridFieldDetailForm.DeletePermissionsFailure', "No delete permissions")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->record->delete();
|
||||||
|
|
||||||
$form->sessionMessage($e->getResult()->message(), 'bad', false);
|
$message = sprintf(
|
||||||
$responseNegotiator = new PjaxResponseNegotiator(array(
|
_t('GridFieldDetailForm.Deleted', 'Deleted %s %s'),
|
||||||
'CurrentForm' => function () use (&$form) {
|
$this->record->i18n_singular_name(),
|
||||||
return $form->forTemplate();
|
htmlspecialchars($title, ENT_QUOTES)
|
||||||
},
|
);
|
||||||
'default' => function () use (&$controller) {
|
|
||||||
return $controller->redirectBack();
|
|
||||||
}
|
|
||||||
));
|
|
||||||
if ($controller->getRequest()->isAjax()) {
|
|
||||||
$controller->getRequest()->addHeader('X-Pjax', 'CurrentForm');
|
|
||||||
}
|
|
||||||
return $responseNegotiator->respond($controller->getRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$toplevelController = $this->getToplevelController();
|
||||||
* @param array $data
|
if ($toplevelController && $toplevelController instanceof LeftAndMain) {
|
||||||
* @param Form $form
|
$backForm = $toplevelController->getEditForm();
|
||||||
* @return HTTPResponse
|
$backForm->sessionMessage($message, 'good', ValidationResult::CAST_HTML);
|
||||||
*/
|
} else {
|
||||||
public function doDelete($data, $form)
|
$form->sessionMessage($message, 'good', ValidationResult::CAST_HTML);
|
||||||
{
|
}
|
||||||
$title = $this->record->Title;
|
|
||||||
try {
|
|
||||||
if (!$this->record->canDelete()) {
|
|
||||||
throw new ValidationException(
|
|
||||||
_t('GridFieldDetailForm.DeletePermissionsFailure',"No delete permissions"));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->record->delete();
|
//when an item is deleted, redirect to the parent controller
|
||||||
} catch (ValidationException $e) {
|
$controller = $this->getToplevelController();
|
||||||
$form->sessionMessage($e->getResult()->message(), 'bad', false);
|
$controller->getRequest()->addHeader('X-Pjax', 'Content'); // Force a content refresh
|
||||||
return $this->getToplevelController()->redirectBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = sprintf(
|
return $controller->redirect($this->getBackLink(), 302); //redirect back to admin section
|
||||||
_t('GridFieldDetailForm.Deleted', 'Deleted %s %s'),
|
}
|
||||||
$this->record->i18n_singular_name(),
|
|
||||||
htmlspecialchars($title, ENT_QUOTES)
|
|
||||||
);
|
|
||||||
|
|
||||||
$toplevelController = $this->getToplevelController();
|
/**
|
||||||
if ($toplevelController && $toplevelController instanceof LeftAndMain) {
|
* @param string $template
|
||||||
$backForm = $toplevelController->getEditForm();
|
* @return $this
|
||||||
$backForm->sessionMessage($message, 'good', false);
|
*/
|
||||||
} else {
|
public function setTemplate($template)
|
||||||
$form->sessionMessage($message, 'good', false);
|
{
|
||||||
}
|
$this->template = $template;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
//when an item is deleted, redirect to the parent controller
|
/**
|
||||||
$controller = $this->getToplevelController();
|
* @return string
|
||||||
$controller->getRequest()->addHeader('X-Pjax', 'Content'); // Force a content refresh
|
*/
|
||||||
|
public function getTemplate()
|
||||||
|
{
|
||||||
|
return $this->template;
|
||||||
|
}
|
||||||
|
|
||||||
return $controller->redirect($this->getBackLink(), 302); //redirect back to admin section
|
/**
|
||||||
}
|
* Get list of templates to use
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getTemplates()
|
||||||
|
{
|
||||||
|
$templates = SSViewer::get_templates_by_class($this, '', __CLASS__);
|
||||||
|
// Prefer any custom template
|
||||||
|
if ($this->getTemplate()) {
|
||||||
|
array_unshift($templates, $this->getTemplate());
|
||||||
|
}
|
||||||
|
return $templates;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $template
|
* @return Controller
|
||||||
* @return $this
|
*/
|
||||||
*/
|
public function getController()
|
||||||
public function setTemplate($template)
|
{
|
||||||
{
|
return $this->popupController;
|
||||||
$this->template = $template;
|
}
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return GridField
|
||||||
*/
|
*/
|
||||||
public function getTemplate()
|
public function getGridField()
|
||||||
{
|
{
|
||||||
return $this->template;
|
return $this->gridField;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of templates to use
|
* @return DataObject
|
||||||
*
|
*/
|
||||||
* @return array
|
public function getRecord()
|
||||||
*/
|
{
|
||||||
public function getTemplates()
|
return $this->record;
|
||||||
{
|
}
|
||||||
$templates = SSViewer::get_templates_by_class($this, '', __CLASS__);
|
|
||||||
// Prefer any custom template
|
|
||||||
if($this->getTemplate()) {
|
|
||||||
array_unshift($templates, $this->getTemplate());
|
|
||||||
}
|
|
||||||
return $templates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Controller
|
* CMS-specific functionality: Passes through navigation breadcrumbs
|
||||||
*/
|
* to the template, and includes the currently edited record (if any).
|
||||||
public function getController()
|
* see {@link LeftAndMain->Breadcrumbs()} for details.
|
||||||
{
|
*
|
||||||
return $this->popupController;
|
* @param boolean $unlinked
|
||||||
}
|
* @return ArrayList
|
||||||
|
*/
|
||||||
|
public function Breadcrumbs($unlinked = false)
|
||||||
|
{
|
||||||
|
if (!$this->popupController->hasMethod('Breadcrumbs')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/** @var ArrayList $items */
|
||||||
* @return GridField
|
$items = $this->popupController->Breadcrumbs($unlinked);
|
||||||
*/
|
if ($this->record && $this->record->ID) {
|
||||||
public function getGridField()
|
$title = ($this->record->Title) ? $this->record->Title : "#{$this->record->ID}";
|
||||||
{
|
$items->push(new ArrayData(array(
|
||||||
return $this->gridField;
|
'Title' => $title,
|
||||||
}
|
'Link' => $this->Link()
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
$items->push(new ArrayData(array(
|
||||||
|
'Title' => sprintf(_t('GridField.NewRecord', 'New %s'), $this->record->i18n_singular_name()),
|
||||||
|
'Link' => false
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return $items;
|
||||||
* @return DataObject
|
}
|
||||||
*/
|
|
||||||
public function getRecord()
|
|
||||||
{
|
|
||||||
return $this->record;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CMS-specific functionality: Passes through navigation breadcrumbs
|
|
||||||
* to the template, and includes the currently edited record (if any).
|
|
||||||
* see {@link LeftAndMain->Breadcrumbs()} for details.
|
|
||||||
*
|
|
||||||
* @param boolean $unlinked
|
|
||||||
* @return ArrayList
|
|
||||||
*/
|
|
||||||
public function Breadcrumbs($unlinked = false)
|
|
||||||
{
|
|
||||||
if (!$this->popupController->hasMethod('Breadcrumbs')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var ArrayList $items */
|
|
||||||
$items = $this->popupController->Breadcrumbs($unlinked);
|
|
||||||
if ($this->record && $this->record->ID) {
|
|
||||||
$title = ($this->record->Title) ? $this->record->Title : "#{$this->record->ID}";
|
|
||||||
$items->push(new ArrayData(array(
|
|
||||||
'Title' => $title,
|
|
||||||
'Link' => $this->Link()
|
|
||||||
)));
|
|
||||||
} else {
|
|
||||||
$items->push(new ArrayData(array(
|
|
||||||
'Title' => sprintf(_t('GridField.NewRecord', 'New %s'), $this->record->i18n_singular_name()),
|
|
||||||
'Link' => false
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $items;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,12 @@ use SilverStripe\ORM\ArrayLib;
|
|||||||
class RequiredFields extends Validator
|
class RequiredFields extends Validator
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of required fields
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
protected $required;
|
protected $required;
|
||||||
protected $useLabels = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass each field to be validated as a seperate argument to the constructor
|
* Pass each field to be validated as a seperate argument to the constructor
|
||||||
@ -84,56 +88,58 @@ class RequiredFields extends Validator
|
|||||||
$valid = ($field->validate($this) && $valid);
|
$valid = ($field->validate($this) && $valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->required) {
|
if (!$this->required) {
|
||||||
foreach ($this->required as $fieldName) {
|
return $valid;
|
||||||
if (!$fieldName) {
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($fieldName instanceof FormField) {
|
foreach ($this->required as $fieldName) {
|
||||||
$formField = $fieldName;
|
if (!$fieldName) {
|
||||||
$fieldName = $fieldName->getName();
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fieldName instanceof FormField) {
|
||||||
|
$formField = $fieldName;
|
||||||
|
$fieldName = $fieldName->getName();
|
||||||
|
} else {
|
||||||
|
$formField = $fields->dataFieldByName($fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// submitted data for file upload fields come back as an array
|
||||||
|
$value = isset($data[$fieldName]) ? $data[$fieldName] : null;
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
if ($formField instanceof FileField && isset($value['error']) && $value['error']) {
|
||||||
|
$error = true;
|
||||||
} else {
|
} else {
|
||||||
$formField = $fields->dataFieldByName($fieldName);
|
$error = (count($value)) ? false : true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// assume a string or integer
|
||||||
|
$error = (strlen($value)) ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
// submitted data for file upload fields come back as an array
|
if ($formField && $error) {
|
||||||
$value = isset($data[$fieldName]) ? $data[$fieldName] : null;
|
$errorMessage = _t(
|
||||||
|
'Form.FIELDISREQUIRED',
|
||||||
if (is_array($value)) {
|
'{name} is required',
|
||||||
if ($formField instanceof FileField && isset($value['error']) && $value['error']) {
|
array(
|
||||||
$error = true;
|
'name' => strip_tags(
|
||||||
} else {
|
'"' . ($formField->Title() ? $formField->Title() : $fieldName) . '"'
|
||||||
$error = (count($value)) ? false : true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// assume a string or integer
|
|
||||||
$error = (strlen($value)) ? false : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($formField && $error) {
|
|
||||||
$errorMessage = _t(
|
|
||||||
'Form.FIELDISREQUIRED',
|
|
||||||
'{name} is required',
|
|
||||||
array(
|
|
||||||
'name' => strip_tags(
|
|
||||||
'"' . ($formField->Title() ? $formField->Title() : $fieldName) . '"'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if ($msg = $formField->getCustomValidationMessage()) {
|
if ($msg = $formField->getCustomValidationMessage()) {
|
||||||
$errorMessage = $msg;
|
$errorMessage = $msg;
|
||||||
}
|
|
||||||
|
|
||||||
$this->validationError(
|
|
||||||
$fieldName,
|
|
||||||
$errorMessage,
|
|
||||||
"required"
|
|
||||||
);
|
|
||||||
|
|
||||||
$valid = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->validationError(
|
||||||
|
$fieldName,
|
||||||
|
$errorMessage,
|
||||||
|
"required"
|
||||||
|
);
|
||||||
|
|
||||||
|
$valid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Forms\Schema;
|
namespace SilverStripe\Forms\Schema;
|
||||||
|
|
||||||
use SilverStripe\Control\Session;
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\Control\HTTPRequest;
|
||||||
use SilverStripe\Forms\CompositeField;
|
use SilverStripe\Forms\CompositeField;
|
||||||
use SilverStripe\Forms\Form;
|
use SilverStripe\Forms\Form;
|
||||||
use SilverStripe\Forms\FormField;
|
use SilverStripe\Forms\FormField;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a {@link Form} as structured data which allows a frontend library to render it.
|
* Represents a {@link Form} as structured data which allows a frontend library to render it.
|
||||||
@ -14,6 +16,69 @@ use SilverStripe\Forms\FormField;
|
|||||||
*/
|
*/
|
||||||
class FormSchema
|
class FormSchema
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Request the schema part
|
||||||
|
*/
|
||||||
|
const PART_SCHEMA = 'schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the state part
|
||||||
|
*/
|
||||||
|
const PART_STATE = 'state';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the errors from a {@see ValidationResult}
|
||||||
|
*/
|
||||||
|
const PART_ERRORS = 'errors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request errors if invalid, or state if valid
|
||||||
|
*/
|
||||||
|
const PART_AUTO = 'auto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a representation of the provided {@link Form} as structured data,
|
||||||
|
* based on the request data.
|
||||||
|
*
|
||||||
|
* @param array|string $schemaParts Array or list of requested parts.
|
||||||
|
* @param string $schemaID ID for this schema. Required.
|
||||||
|
* @param Form $form Required for 'state' or 'schema' response
|
||||||
|
* @param ValidationResult $result Required for 'error' response
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMultipartSchema($schemaParts, $schemaID, Form $form = null, ValidationResult $result = null)
|
||||||
|
{
|
||||||
|
if (!is_array($schemaParts)) {
|
||||||
|
$schemaParts = preg_split('#\s*,\s*#', $schemaParts) ?: [];
|
||||||
|
}
|
||||||
|
$wantSchema = in_array('schema', $schemaParts);
|
||||||
|
$wantState = in_array('state', $schemaParts);
|
||||||
|
$wantErrors = in_array('errors', $schemaParts);
|
||||||
|
$auto = in_array('auto', $schemaParts);
|
||||||
|
|
||||||
|
// Require ID
|
||||||
|
if (empty($schemaID)) {
|
||||||
|
throw new InvalidArgumentException("schemaID is required");
|
||||||
|
}
|
||||||
|
$return = ['id' => $schemaID];
|
||||||
|
|
||||||
|
// Default to schema if not set
|
||||||
|
if ($form && ($wantSchema || empty($schemaParts))) {
|
||||||
|
$return['schema'] = $this->getSchema($form);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return 'state' if requested, or if there are errors and 'auto'
|
||||||
|
if ($form && ($wantState || ($auto && !$result))) {
|
||||||
|
$return['state'] = $this->getState($form);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return errors if 'errors' or 'auto'
|
||||||
|
if ($result && ($wantErrors || $auto)) {
|
||||||
|
$return['errors'] = $this->getErrors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the schema for this form as a nested array.
|
* Gets the schema for this form as a nested array.
|
||||||
@ -55,18 +120,9 @@ class FormSchema
|
|||||||
*/
|
*/
|
||||||
public function getState(Form $form)
|
public function getState(Form $form)
|
||||||
{
|
{
|
||||||
// Ensure that session errors are populated within form field messages
|
|
||||||
$form->setupFormErrors();
|
|
||||||
|
|
||||||
// @todo - Replace with ValidationResult handling
|
|
||||||
// Currently tri-state; null (unsubmitted), true (submitted-valid), false (submitted-invalid)
|
|
||||||
$errors = Session::get("FormInfo.{$form->FormName()}.errors");
|
|
||||||
$valid = isset($errors) ? empty($errors) : null;
|
|
||||||
|
|
||||||
$state = [
|
$state = [
|
||||||
'id' => $form->FormName(),
|
'id' => $form->FormName(),
|
||||||
'fields' => [],
|
'fields' => [],
|
||||||
'valid' => $valid,
|
|
||||||
'messages' => [],
|
'messages' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -76,17 +132,46 @@ class FormSchema
|
|||||||
$this->getFieldStates($form->Actions())
|
$this->getFieldStates($form->Actions())
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($message = $form->Message()) {
|
if ($message = $form->getSchemaMessage()) {
|
||||||
$state['messages'][] = [
|
$state['messages'][] = $message;
|
||||||
// TODO Make form / field messages not always stored as html
|
|
||||||
'value' => ['html' => $message],
|
|
||||||
'type' => $form->MessageType(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ValidationResult $result
|
||||||
|
* @return array List of errors
|
||||||
|
*/
|
||||||
|
public function getErrors(ValidationResult $result)
|
||||||
|
{
|
||||||
|
$messages = [];
|
||||||
|
foreach ($result->getMessages() as $message) {
|
||||||
|
$messages[] = $this->getSchemaForMessage($message);
|
||||||
|
}
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return form schema for encoded validation message
|
||||||
|
*
|
||||||
|
* @param array $message Internal ValidationResult format for this message
|
||||||
|
* @return array Form schema format for this message
|
||||||
|
*/
|
||||||
|
protected function getSchemaForMessage($message)
|
||||||
|
{
|
||||||
|
// Form schema messages treat simple strings as plain text, so nest for html messages
|
||||||
|
$value = $message['message'];
|
||||||
|
if ($message['messageCast'] === ValidationResult::CAST_HTML) {
|
||||||
|
$value = ['html' => $message];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'value' => $value,
|
||||||
|
'type' => $message['messageType'],
|
||||||
|
'field' => empty($message['fieldName']) ? null : $message['fieldName'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected function getFieldStates($fields)
|
protected function getFieldStates($fields)
|
||||||
{
|
{
|
||||||
$states = [];
|
$states = [];
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace SilverStripe\Forms;
|
namespace SilverStripe\Forms;
|
||||||
|
|
||||||
use SilverStripe\Core\Object;
|
use SilverStripe\Core\Object;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This validation class handles all form and custom form validation through the use of Required
|
* This validation class handles all form and custom form validation through the use of Required
|
||||||
@ -12,40 +13,42 @@ use SilverStripe\Core\Object;
|
|||||||
abstract class Validator extends Object
|
abstract class Validator extends Object
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->resetResult();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Form $form
|
* @var Form $form
|
||||||
*/
|
*/
|
||||||
protected $form;
|
protected $form;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array $errors
|
* @var ValidationResult $result
|
||||||
*/
|
*/
|
||||||
protected $errors;
|
protected $result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Form $form
|
* @param Form $form
|
||||||
*
|
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setForm($form)
|
public function setForm($form)
|
||||||
{
|
{
|
||||||
$this->form = $form;
|
$this->form = $form;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns any errors there may be.
|
* Returns any errors there may be.
|
||||||
*
|
*
|
||||||
* @return null|array
|
* @return ValidationResult
|
||||||
*/
|
*/
|
||||||
public function validate()
|
public function validate()
|
||||||
{
|
{
|
||||||
$this->errors = null;
|
$this->resetResult();
|
||||||
|
|
||||||
$this->php($this->form->getData());
|
$this->php($this->form->getData());
|
||||||
|
return $this->result;
|
||||||
return $this->errors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,17 +58,22 @@ abstract class Validator extends Object
|
|||||||
*
|
*
|
||||||
* See {@link getErrors()} for details.
|
* See {@link getErrors()} for details.
|
||||||
*
|
*
|
||||||
* @param string $fieldName
|
* @param string $fieldName Field name for this error
|
||||||
* @param string $errorMessage
|
* @param string $message The message string
|
||||||
* @param string $errorMessageType
|
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
||||||
|
* class to the form, so other values can be used if desired.
|
||||||
|
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||||
|
* Bool values will be treated as plain text flag.
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function validationError($fieldName, $errorMessage, $errorMessageType = '')
|
public function validationError(
|
||||||
{
|
$fieldName,
|
||||||
$this->errors[] = array(
|
$message,
|
||||||
'fieldName' => $fieldName,
|
$messageType = ValidationResult::TYPE_ERROR,
|
||||||
'message' => $errorMessage,
|
$cast = ValidationResult::CAST_TEXT
|
||||||
'messageType' => $errorMessageType,
|
) {
|
||||||
);
|
$this->result->addFieldError($fieldName, $message, $messageType, null, $cast);
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,6 +85,7 @@ abstract class Validator extends Object
|
|||||||
* 'fieldName' => '[form field name]',
|
* 'fieldName' => '[form field name]',
|
||||||
* 'message' => '[validation error message]',
|
* 'message' => '[validation error message]',
|
||||||
* 'messageType' => '[bad|message|validation|required]',
|
* 'messageType' => '[bad|message|validation|required]',
|
||||||
|
* 'messageCast' => '[text|html]'
|
||||||
* )
|
* )
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
@ -84,32 +93,20 @@ abstract class Validator extends Object
|
|||||||
*/
|
*/
|
||||||
public function getErrors()
|
public function getErrors()
|
||||||
{
|
{
|
||||||
return $this->errors;
|
if ($this->result) {
|
||||||
|
return $this->result->getMessages();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $fieldName
|
* Get last validation result
|
||||||
* @param array $data
|
*
|
||||||
|
* @return ValidationResult
|
||||||
*/
|
*/
|
||||||
public function requireField($fieldName, $data)
|
public function getResult()
|
||||||
{
|
{
|
||||||
if (is_array($data[$fieldName]) && count($data[$fieldName])) {
|
return $this->result;
|
||||||
foreach ($data[$fieldName] as $componentKey => $componentValue) {
|
|
||||||
if (!strlen($componentValue)) {
|
|
||||||
$this->validationError(
|
|
||||||
$fieldName,
|
|
||||||
sprintf('%s %s is required', $fieldName, $componentKey),
|
|
||||||
'required'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} elseif (!strlen($data[$fieldName])) {
|
|
||||||
$this->validationError(
|
|
||||||
$fieldName,
|
|
||||||
sprintf('%s is required', $fieldName),
|
|
||||||
'required'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,4 +128,15 @@ abstract class Validator extends Object
|
|||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
abstract public function php($data);
|
abstract public function php($data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear current result
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
protected function resetResult()
|
||||||
|
{
|
||||||
|
$this->result = ValidationResult::create();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,7 +459,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
}
|
}
|
||||||
if ($sourceObject->manyMany()) {
|
if ($sourceObject->manyMany()) {
|
||||||
foreach ($sourceObject->manyMany() as $name => $type) {
|
foreach ($sourceObject->manyMany() as $name => $type) {
|
||||||
//many_many include belongs_many_many
|
//many_many include belongs_many_many
|
||||||
$this->duplicateRelations($sourceObject, $destinationObject, $name);
|
$this->duplicateRelations($sourceObject, $destinationObject, $name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1139,11 +1139,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
|
|
||||||
if ($defaults) {
|
if ($defaults) {
|
||||||
foreach ($defaults as $fieldName => $fieldValue) {
|
foreach ($defaults as $fieldName => $fieldValue) {
|
||||||
// SRM 2007-03-06: Stricter check
|
// SRM 2007-03-06: Stricter check
|
||||||
if (!isset($this->$fieldName) || $this->$fieldName === null) {
|
if (!isset($this->$fieldName) || $this->$fieldName === null) {
|
||||||
$this->$fieldName = $fieldValue;
|
$this->$fieldName = $fieldValue;
|
||||||
}
|
}
|
||||||
// Set many-many defaults with an array of ids
|
// Set many-many defaults with an array of ids
|
||||||
if (is_array($fieldValue) && $this->getSchema()->manyManyComponent(static::class, $fieldName)) {
|
if (is_array($fieldValue) && $this->getSchema()->manyManyComponent(static::class, $fieldName)) {
|
||||||
/** @var ManyManyList $manyManyJoin */
|
/** @var ManyManyList $manyManyJoin */
|
||||||
$manyManyJoin = $this->$fieldName();
|
$manyManyJoin = $this->$fieldName();
|
||||||
@ -1170,19 +1170,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
if ($this->ObsoleteClassName) {
|
if ($this->ObsoleteClassName) {
|
||||||
return new ValidationException(
|
return new ValidationException(
|
||||||
"Object is of class '{$this->ObsoleteClassName}' which doesn't exist - ".
|
"Object is of class '{$this->ObsoleteClassName}' which doesn't exist - ".
|
||||||
"you need to change the ClassName before you can write it",
|
"you need to change the ClassName before you can write it"
|
||||||
E_USER_WARNING
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->config()->get('validation_enabled')) {
|
if ($this->config()->get('validation_enabled')) {
|
||||||
$result = $this->validate();
|
$result = $this->validate();
|
||||||
if (!$result->valid()) {
|
if (!$result->isValid()) {
|
||||||
return new ValidationException(
|
return new ValidationException($result);
|
||||||
$result,
|
|
||||||
$result->message(),
|
|
||||||
E_USER_WARNING
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -2356,7 +2351,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
|
'before' => array_key_exists($name, $this->original) ? $this->original[$name] : null,
|
||||||
'after' => array_key_exists($name, $this->record) ? $this->record[$name] : null,
|
'after' => array_key_exists($name, $this->record) ? $this->record[$name] : null,
|
||||||
'level' => $level
|
'level' => $level
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3382,7 +3377,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
}
|
}
|
||||||
$types = array(
|
$types = array(
|
||||||
'db' => (array)Config::inst()->get($ancestorClass, 'db', Config::UNINHERITED)
|
'db' => (array)Config::inst()->get($ancestorClass, 'db', Config::UNINHERITED)
|
||||||
);
|
);
|
||||||
if ($includerelations) {
|
if ($includerelations) {
|
||||||
$types['has_one'] = (array)Config::inst()->get($ancestorClass, 'has_one', Config::UNINHERITED);
|
$types['has_one'] = (array)Config::inst()->get($ancestorClass, 'has_one', Config::UNINHERITED);
|
||||||
$types['has_many'] = (array)Config::inst()->get($ancestorClass, 'has_many', Config::UNINHERITED);
|
$types['has_many'] = (array)Config::inst()->get($ancestorClass, 'has_many', Config::UNINHERITED);
|
||||||
|
@ -25,199 +25,199 @@ use Exception;
|
|||||||
class Hierarchy extends DataExtension
|
class Hierarchy extends DataExtension
|
||||||
{
|
{
|
||||||
|
|
||||||
protected $markedNodes;
|
protected $markedNodes;
|
||||||
|
|
||||||
protected $markingFilter;
|
protected $markingFilter;
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
protected $_cache_numChildren;
|
protected $_cache_numChildren;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The lower bounds for the amount of nodes to mark. If set, the logic will expand nodes until it reaches at least
|
* The lower bounds for the amount of nodes to mark. If set, the logic will expand nodes until it reaches at least
|
||||||
* this number, and then stops. Root nodes will always show regardless of this settting. Further nodes can be
|
* this number, and then stops. Root nodes will always show regardless of this settting. Further nodes can be
|
||||||
* lazy-loaded via ajax. This isn't a hard limit. Example: On a value of 10, with 20 root nodes, each having 30
|
* lazy-loaded via ajax. This isn't a hard limit. Example: On a value of 10, with 20 root nodes, each having 30
|
||||||
* children, the actual node count will be 50 (all root nodes plus first expanded child).
|
* children, the actual node count will be 50 (all root nodes plus first expanded child).
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
private static $node_threshold_total = 50;
|
private static $node_threshold_total = 50;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Limit on the maximum children a specific node can display. Serves as a hard limit to avoid exceeding available
|
* Limit on the maximum children a specific node can display. Serves as a hard limit to avoid exceeding available
|
||||||
* server resources in generating the tree, and browser resources in rendering it. Nodes with children exceeding
|
* server resources in generating the tree, and browser resources in rendering it. Nodes with children exceeding
|
||||||
* this value typically won't display any children, although this is configurable through the $nodeCountCallback
|
* this value typically won't display any children, although this is configurable through the $nodeCountCallback
|
||||||
* parameter in {@link getChildrenAsUL()}. "Root" nodes will always show all children, regardless of this setting.
|
* parameter in {@link getChildrenAsUL()}. "Root" nodes will always show all children, regardless of this setting.
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
private static $node_threshold_leaf = 250;
|
private static $node_threshold_leaf = 250;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of classnames to exclude from display in both the CMS and front end
|
* A list of classnames to exclude from display in both the CMS and front end
|
||||||
* displays. ->Children() and ->AllChildren affected.
|
* displays. ->Children() and ->AllChildren affected.
|
||||||
* Especially useful for big sets of pages like listings
|
* Especially useful for big sets of pages like listings
|
||||||
* If you use this, and still need the classes to be editable
|
* If you use this, and still need the classes to be editable
|
||||||
* then add a model admin for the class
|
* then add a model admin for the class
|
||||||
* Note: Does not filter subclasses (non-inheriting)
|
* Note: Does not filter subclasses (non-inheriting)
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
* @config
|
* @config
|
||||||
*/
|
*/
|
||||||
private static $hide_from_hierarchy = array();
|
private static $hide_from_hierarchy = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of classnames to exclude from display in the page tree views of the CMS,
|
* A list of classnames to exclude from display in the page tree views of the CMS,
|
||||||
* unlike $hide_from_hierarchy above which effects both CMS and front end.
|
* unlike $hide_from_hierarchy above which effects both CMS and front end.
|
||||||
* Especially useful for big sets of pages like listings
|
* Especially useful for big sets of pages like listings
|
||||||
* If you use this, and still need the classes to be editable
|
* If you use this, and still need the classes to be editable
|
||||||
* then add a model admin for the class
|
* then add a model admin for the class
|
||||||
* Note: Does not filter subclasses (non-inheriting)
|
* Note: Does not filter subclasses (non-inheriting)
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
* @config
|
* @config
|
||||||
*/
|
*/
|
||||||
private static $hide_from_cms_tree = array();
|
private static $hide_from_cms_tree = array();
|
||||||
|
|
||||||
public static function get_extra_config($class, $extension, $args)
|
public static function get_extra_config($class, $extension, $args)
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'has_one' => array('Parent' => $class)
|
'has_one' => array('Parent' => $class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the owner object - check for existence of infinite loops.
|
* Validate the owner object - check for existence of infinite loops.
|
||||||
*
|
*
|
||||||
* @param ValidationResult $validationResult
|
* @param ValidationResult $validationResult
|
||||||
*/
|
*/
|
||||||
public function validate(ValidationResult $validationResult)
|
public function validate(ValidationResult $validationResult)
|
||||||
{
|
{
|
||||||
// The object is new, won't be looping.
|
// The object is new, won't be looping.
|
||||||
if (!$this->owner->ID) {
|
if (!$this->owner->ID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The object has no parent, won't be looping.
|
// The object has no parent, won't be looping.
|
||||||
if (!$this->owner->ParentID) {
|
if (!$this->owner->ParentID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The parent has not changed, skip the check for performance reasons.
|
// The parent has not changed, skip the check for performance reasons.
|
||||||
if (!$this->owner->isChanged('ParentID')) {
|
if (!$this->owner->isChanged('ParentID')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the hierarchy upwards until we reach the top, or until we reach the originating node again.
|
// Walk the hierarchy upwards until we reach the top, or until we reach the originating node again.
|
||||||
$node = $this->owner;
|
$node = $this->owner;
|
||||||
while($node) {
|
while ($node) {
|
||||||
if ($node->ParentID==$this->owner->ID) {
|
if ($node->ParentID==$this->owner->ID) {
|
||||||
// Hierarchy is looping.
|
// Hierarchy is looping.
|
||||||
$validationResult->addError(
|
$validationResult->addError(
|
||||||
_t(
|
_t(
|
||||||
'Hierarchy.InfiniteLoopNotAllowed',
|
'Hierarchy.InfiniteLoopNotAllowed',
|
||||||
'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this',
|
'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this',
|
||||||
'First argument is the class that makes up the hierarchy.',
|
'First argument is the class that makes up the hierarchy.',
|
||||||
array('type' => $this->owner->class)
|
array('type' => $this->owner->class)
|
||||||
),
|
),
|
||||||
'bad',
|
'bad',
|
||||||
'INFINITE_LOOP'
|
'INFINITE_LOOP'
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$node = $node->ParentID ? $node->Parent() : null;
|
$node = $node->ParentID ? $node->Parent() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point the $validationResult contains the response.
|
// At this point the $validationResult contains the response.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the children of this DataObject as an XHTML UL. This will be called recursively on each child, so if they
|
* Returns the children of this DataObject as an XHTML UL. This will be called recursively on each child, so if they
|
||||||
* have children they will be displayed as a UL inside a LI.
|
* have children they will be displayed as a UL inside a LI.
|
||||||
*
|
*
|
||||||
* @param string $attributes Attributes to add to the UL
|
* @param string $attributes Attributes to add to the UL
|
||||||
* @param string|callable $titleEval PHP code to evaluate to start each child - this should include '<li>'
|
* @param string|callable $titleEval PHP code to evaluate to start each child - this should include '<li>'
|
||||||
* @param string $extraArg Extra arguments that will be passed on to children, for if they overload this function
|
* @param string $extraArg Extra arguments that will be passed on to children, for if they overload this function
|
||||||
* @param bool $limitToMarked Display only marked children
|
* @param bool $limitToMarked Display only marked children
|
||||||
* @param string $childrenMethod The name of the method used to get children from each object
|
* @param string $childrenMethod The name of the method used to get children from each object
|
||||||
* @param string $numChildrenMethod
|
* @param string $numChildrenMethod
|
||||||
* @param bool $rootCall Set to true for this first call, and then to false for calls inside the recursion.
|
* @param bool $rootCall Set to true for this first call, and then to false for calls inside the recursion.
|
||||||
* You should not change this.
|
* You should not change this.
|
||||||
* @param int $nodeCountThreshold See {@link self::$node_threshold_total}
|
* @param int $nodeCountThreshold See {@link self::$node_threshold_total}
|
||||||
* @param callable $nodeCountCallback Called with the node count, which gives the callback an opportunity to
|
* @param callable $nodeCountCallback Called with the node count, which gives the callback an opportunity to
|
||||||
* intercept the query. Useful e.g. to avoid excessive children listings (Arguments: $parent, $numChildren)
|
* intercept the query. Useful e.g. to avoid excessive children listings (Arguments: $parent, $numChildren)
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getChildrenAsUL(
|
public function getChildrenAsUL(
|
||||||
$attributes = "",
|
$attributes = "",
|
||||||
$titleEval = '"<li>" . $child->Title',
|
$titleEval = '"<li>" . $child->Title',
|
||||||
$extraArg = null,
|
$extraArg = null,
|
||||||
$limitToMarked = false,
|
$limitToMarked = false,
|
||||||
$childrenMethod = "AllChildrenIncludingDeleted",
|
$childrenMethod = "AllChildrenIncludingDeleted",
|
||||||
$numChildrenMethod = "numChildren",
|
$numChildrenMethod = "numChildren",
|
||||||
$rootCall = true,
|
$rootCall = true,
|
||||||
$nodeCountThreshold = null,
|
$nodeCountThreshold = null,
|
||||||
$nodeCountCallback = null
|
$nodeCountCallback = null
|
||||||
) {
|
) {
|
||||||
if(!is_numeric($nodeCountThreshold)) {
|
if (!is_numeric($nodeCountThreshold)) {
|
||||||
$nodeCountThreshold = Config::inst()->get(__CLASS__, 'node_threshold_total');
|
$nodeCountThreshold = Config::inst()->get(__CLASS__, 'node_threshold_total');
|
||||||
}
|
}
|
||||||
|
|
||||||
if($limitToMarked && $rootCall) {
|
if ($limitToMarked && $rootCall) {
|
||||||
$this->markingFinished($numChildrenMethod);
|
$this->markingFinished($numChildrenMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if($nodeCountCallback) {
|
if ($nodeCountCallback) {
|
||||||
$nodeCountWarning = $nodeCountCallback($this->owner, $this->owner->$numChildrenMethod());
|
$nodeCountWarning = $nodeCountCallback($this->owner, $this->owner->$numChildrenMethod());
|
||||||
if ($nodeCountWarning) {
|
if ($nodeCountWarning) {
|
||||||
return $nodeCountWarning;
|
return $nodeCountWarning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if($this->owner->hasMethod($childrenMethod)) {
|
if ($this->owner->hasMethod($childrenMethod)) {
|
||||||
$children = $this->owner->$childrenMethod($extraArg);
|
$children = $this->owner->$childrenMethod($extraArg);
|
||||||
} else {
|
} else {
|
||||||
$children = null;
|
$children = null;
|
||||||
user_error(sprintf(
|
user_error(sprintf(
|
||||||
"Can't find the method '%s' on class '%s' for getting tree children",
|
"Can't find the method '%s' on class '%s' for getting tree children",
|
||||||
$childrenMethod,
|
$childrenMethod,
|
||||||
get_class($this->owner)
|
get_class($this->owner)
|
||||||
), E_USER_ERROR);
|
), E_USER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = null;
|
$output = null;
|
||||||
if($children) {
|
if ($children) {
|
||||||
if($attributes) {
|
if ($attributes) {
|
||||||
$attributes = " $attributes";
|
$attributes = " $attributes";
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = "<ul$attributes>\n";
|
$output = "<ul$attributes>\n";
|
||||||
|
|
||||||
foreach($children as $child) {
|
foreach ($children as $child) {
|
||||||
if(!$limitToMarked || $child->isMarked()) {
|
if (!$limitToMarked || $child->isMarked()) {
|
||||||
$foundAChild = true;
|
$foundAChild = true;
|
||||||
if(is_callable($titleEval)) {
|
if (is_callable($titleEval)) {
|
||||||
$output .= $titleEval($child, $numChildrenMethod);
|
$output .= $titleEval($child, $numChildrenMethod);
|
||||||
} else {
|
} else {
|
||||||
$output .= eval("return $titleEval;");
|
$output .= eval("return $titleEval;");
|
||||||
}
|
}
|
||||||
$output .= "\n";
|
$output .= "\n";
|
||||||
|
|
||||||
$numChildren = $child->$numChildrenMethod();
|
$numChildren = $child->$numChildrenMethod();
|
||||||
|
|
||||||
if (// Always traverse into opened nodes (they might be exposed as parents of search results)
|
if (// Always traverse into opened nodes (they might be exposed as parents of search results)
|
||||||
$child->isExpanded()
|
$child->isExpanded()
|
||||||
// Only traverse into children if we haven't reached the maximum node count already.
|
// Only traverse into children if we haven't reached the maximum node count already.
|
||||||
// Otherwise, the remaining nodes are lazy loaded via ajax.
|
// Otherwise, the remaining nodes are lazy loaded via ajax.
|
||||||
&& $child->isMarked()
|
&& $child->isMarked()
|
||||||
) {
|
) {
|
||||||
// Additionally check if node count requirements are met
|
// Additionally check if node count requirements are met
|
||||||
$nodeCountWarning = $nodeCountCallback ? $nodeCountCallback($child, $numChildren) : null;
|
$nodeCountWarning = $nodeCountCallback ? $nodeCountCallback($child, $numChildren) : null;
|
||||||
if($nodeCountWarning) {
|
if ($nodeCountWarning) {
|
||||||
$output .= $nodeCountWarning;
|
$output .= $nodeCountWarning;
|
||||||
$child->markClosed();
|
$child->markClosed();
|
||||||
} else {
|
} else {
|
||||||
$output .= $child->getChildrenAsUL(
|
$output .= $child->getChildrenAsUL(
|
||||||
"",
|
"",
|
||||||
$titleEval,
|
$titleEval,
|
||||||
@ -228,770 +228,770 @@ class Hierarchy extends DataExtension
|
|||||||
false,
|
false,
|
||||||
$nodeCountThreshold
|
$nodeCountThreshold
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} elseif($child->isTreeOpened()) {
|
} elseif ($child->isTreeOpened()) {
|
||||||
// Since we're not loading children, don't mark it as open either
|
// Since we're not loading children, don't mark it as open either
|
||||||
$child->markClosed();
|
$child->markClosed();
|
||||||
}
|
}
|
||||||
$output .= "</li>\n";
|
$output .= "</li>\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$output .= "</ul>\n";
|
$output .= "</ul>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($foundAChild) && $foundAChild) {
|
if (isset($foundAChild) && $foundAChild) {
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a segment of the tree, by calling mark().
|
* Mark a segment of the tree, by calling mark().
|
||||||
*
|
*
|
||||||
* The method performs a breadth-first traversal until the number of nodes is more than minCount. This is used to
|
* The method performs a breadth-first traversal until the number of nodes is more than minCount. This is used to
|
||||||
* get a limited number of tree nodes to show in the CMS initially.
|
* get a limited number of tree nodes to show in the CMS initially.
|
||||||
*
|
*
|
||||||
* This method returns the number of nodes marked. After this method is called other methods can check
|
* This method returns the number of nodes marked. After this method is called other methods can check
|
||||||
* {@link isExpanded()} and {@link isMarked()} on individual nodes.
|
* {@link isExpanded()} and {@link isMarked()} on individual nodes.
|
||||||
*
|
*
|
||||||
* @param int $nodeCountThreshold See {@link getChildrenAsUL()}
|
* @param int $nodeCountThreshold See {@link getChildrenAsUL()}
|
||||||
* @param mixed $context
|
* @param mixed $context
|
||||||
* @param string $childrenMethod
|
* @param string $childrenMethod
|
||||||
* @param string $numChildrenMethod
|
* @param string $numChildrenMethod
|
||||||
* @return int The actual number of nodes marked.
|
* @return int The actual number of nodes marked.
|
||||||
*/
|
*/
|
||||||
public function markPartialTree(
|
public function markPartialTree(
|
||||||
$nodeCountThreshold = 30,
|
$nodeCountThreshold = 30,
|
||||||
$context = null,
|
$context = null,
|
||||||
$childrenMethod = "AllChildrenIncludingDeleted",
|
$childrenMethod = "AllChildrenIncludingDeleted",
|
||||||
$numChildrenMethod = "numChildren"
|
$numChildrenMethod = "numChildren"
|
||||||
) {
|
) {
|
||||||
if (!is_numeric($nodeCountThreshold)) {
|
if (!is_numeric($nodeCountThreshold)) {
|
||||||
$nodeCountThreshold = 30;
|
$nodeCountThreshold = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->markedNodes = array($this->owner->ID => $this->owner);
|
$this->markedNodes = array($this->owner->ID => $this->owner);
|
||||||
$this->owner->markUnexpanded();
|
$this->owner->markUnexpanded();
|
||||||
|
|
||||||
// foreach can't handle an ever-growing $nodes list
|
// foreach can't handle an ever-growing $nodes list
|
||||||
while(list($id, $node) = each($this->markedNodes)) {
|
while (list($id, $node) = each($this->markedNodes)) {
|
||||||
$children = $this->markChildren($node, $context, $childrenMethod, $numChildrenMethod);
|
$children = $this->markChildren($node, $context, $childrenMethod, $numChildrenMethod);
|
||||||
if($nodeCountThreshold && sizeof($this->markedNodes) > $nodeCountThreshold) {
|
if ($nodeCountThreshold && sizeof($this->markedNodes) > $nodeCountThreshold) {
|
||||||
// Undo marking children as opened since they're lazy loaded
|
// Undo marking children as opened since they're lazy loaded
|
||||||
if ($children) {
|
if ($children) {
|
||||||
foreach ($children as $child) {
|
foreach ($children as $child) {
|
||||||
$child->markClosed();
|
$child->markClosed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sizeof($this->markedNodes);
|
return sizeof($this->markedNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the marking to only those object with $node->$parameterName == $parameterValue
|
* Filter the marking to only those object with $node->$parameterName == $parameterValue
|
||||||
*
|
*
|
||||||
* @param string $parameterName The parameter on each node to check when marking.
|
* @param string $parameterName The parameter on each node to check when marking.
|
||||||
* @param mixed $parameterValue The value the parameter must be to be marked.
|
* @param mixed $parameterValue The value the parameter must be to be marked.
|
||||||
*/
|
*/
|
||||||
public function setMarkingFilter($parameterName, $parameterValue)
|
public function setMarkingFilter($parameterName, $parameterValue)
|
||||||
{
|
{
|
||||||
$this->markingFilter = array(
|
$this->markingFilter = array(
|
||||||
"parameter" => $parameterName,
|
"parameter" => $parameterName,
|
||||||
"value" => $parameterValue
|
"value" => $parameterValue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the marking to only those where the function returns true. The node in question will be passed to the
|
* Filter the marking to only those where the function returns true. The node in question will be passed to the
|
||||||
* function.
|
* function.
|
||||||
*
|
*
|
||||||
* @param string $funcName The name of the function to call
|
* @param string $funcName The name of the function to call
|
||||||
*/
|
*/
|
||||||
public function setMarkingFilterFunction($funcName)
|
public function setMarkingFilterFunction($funcName)
|
||||||
{
|
{
|
||||||
$this->markingFilter = array(
|
$this->markingFilter = array(
|
||||||
"func" => $funcName,
|
"func" => $funcName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the marking filter matches on the given node.
|
* Returns true if the marking filter matches on the given node.
|
||||||
*
|
*
|
||||||
* @param DataObject $node Node to check
|
* @param DataObject $node Node to check
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function markingFilterMatches($node)
|
public function markingFilterMatches($node)
|
||||||
{
|
{
|
||||||
if(!$this->markingFilter) {
|
if (!$this->markingFilter) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($this->markingFilter['parameter']) && $parameterName = $this->markingFilter['parameter']) {
|
if (isset($this->markingFilter['parameter']) && $parameterName = $this->markingFilter['parameter']) {
|
||||||
if(is_array($this->markingFilter['value'])){
|
if (is_array($this->markingFilter['value'])) {
|
||||||
$ret = false;
|
$ret = false;
|
||||||
foreach($this->markingFilter['value'] as $value) {
|
foreach ($this->markingFilter['value'] as $value) {
|
||||||
$ret = $ret||$node->$parameterName==$value;
|
$ret = $ret||$node->$parameterName==$value;
|
||||||
if($ret == true) {
|
if ($ret == true) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $ret;
|
return $ret;
|
||||||
} else {
|
} else {
|
||||||
return ($node->$parameterName == $this->markingFilter['value']);
|
return ($node->$parameterName == $this->markingFilter['value']);
|
||||||
}
|
}
|
||||||
} else if ($func = $this->markingFilter['func']) {
|
} elseif ($func = $this->markingFilter['func']) {
|
||||||
return call_user_func($func, $node);
|
return call_user_func($func, $node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all children of the given node that match the marking filter.
|
* Mark all children of the given node that match the marking filter.
|
||||||
*
|
*
|
||||||
* @param DataObject $node Parent node
|
* @param DataObject $node Parent node
|
||||||
* @param mixed $context
|
* @param mixed $context
|
||||||
* @param string $childrenMethod The name of the instance method to call to get the object's list of children
|
* @param string $childrenMethod The name of the instance method to call to get the object's list of children
|
||||||
* @param string $numChildrenMethod The name of the instance method to call to count the object's children
|
* @param string $numChildrenMethod The name of the instance method to call to count the object's children
|
||||||
* @return DataList
|
* @return DataList
|
||||||
*/
|
*/
|
||||||
public function markChildren(
|
public function markChildren(
|
||||||
$node,
|
$node,
|
||||||
$context = null,
|
$context = null,
|
||||||
$childrenMethod = "AllChildrenIncludingDeleted",
|
$childrenMethod = "AllChildrenIncludingDeleted",
|
||||||
$numChildrenMethod = "numChildren"
|
$numChildrenMethod = "numChildren"
|
||||||
) {
|
) {
|
||||||
if($node->hasMethod($childrenMethod)) {
|
if ($node->hasMethod($childrenMethod)) {
|
||||||
$children = $node->$childrenMethod($context);
|
$children = $node->$childrenMethod($context);
|
||||||
} else {
|
} else {
|
||||||
$children = null;
|
$children = null;
|
||||||
user_error(sprintf(
|
user_error(sprintf(
|
||||||
"Can't find the method '%s' on class '%s' for getting tree children",
|
"Can't find the method '%s' on class '%s' for getting tree children",
|
||||||
$childrenMethod,
|
$childrenMethod,
|
||||||
get_class($node)
|
get_class($node)
|
||||||
), E_USER_ERROR);
|
), E_USER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
$node->markExpanded();
|
$node->markExpanded();
|
||||||
if($children) {
|
if ($children) {
|
||||||
foreach($children as $child) {
|
foreach ($children as $child) {
|
||||||
$markingMatches = $this->markingFilterMatches($child);
|
$markingMatches = $this->markingFilterMatches($child);
|
||||||
if($markingMatches) {
|
if ($markingMatches) {
|
||||||
// Mark a child node as unexpanded if it has children and has not already been expanded
|
// Mark a child node as unexpanded if it has children and has not already been expanded
|
||||||
if($child->$numChildrenMethod() && !$child->isExpanded()) {
|
if ($child->$numChildrenMethod() && !$child->isExpanded()) {
|
||||||
$child->markUnexpanded();
|
$child->markUnexpanded();
|
||||||
} else {
|
} else {
|
||||||
$child->markExpanded();
|
$child->markExpanded();
|
||||||
}
|
}
|
||||||
$this->markedNodes[$child->ID] = $child;
|
$this->markedNodes[$child->ID] = $child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $children;
|
return $children;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure marked nodes that have children are also marked expanded. Call this after marking but before iterating
|
* Ensure marked nodes that have children are also marked expanded. Call this after marking but before iterating
|
||||||
* over the tree.
|
* over the tree.
|
||||||
*
|
*
|
||||||
* @param string $numChildrenMethod The name of the instance method to call to count the object's children
|
* @param string $numChildrenMethod The name of the instance method to call to count the object's children
|
||||||
*/
|
*/
|
||||||
protected function markingFinished($numChildrenMethod = "numChildren")
|
protected function markingFinished($numChildrenMethod = "numChildren")
|
||||||
{
|
{
|
||||||
// Mark childless nodes as expanded.
|
// Mark childless nodes as expanded.
|
||||||
if($this->markedNodes) {
|
if ($this->markedNodes) {
|
||||||
foreach($this->markedNodes as $id => $node) {
|
foreach ($this->markedNodes as $id => $node) {
|
||||||
if(!$node->isExpanded() && !$node->$numChildrenMethod()) {
|
if (!$node->isExpanded() && !$node->$numChildrenMethod()) {
|
||||||
$node->markExpanded();
|
$node->markExpanded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return CSS classes of 'unexpanded', 'closed', both, or neither, as well as a 'jstree-*' state depending on the
|
* Return CSS classes of 'unexpanded', 'closed', both, or neither, as well as a 'jstree-*' state depending on the
|
||||||
* marking of this DataObject.
|
* marking of this DataObject.
|
||||||
*
|
*
|
||||||
* @param string $numChildrenMethod The name of the instance method to call to count the object's children
|
* @param string $numChildrenMethod The name of the instance method to call to count the object's children
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function markingClasses($numChildrenMethod = "numChildren")
|
public function markingClasses($numChildrenMethod = "numChildren")
|
||||||
{
|
{
|
||||||
$classes = '';
|
$classes = '';
|
||||||
if(!$this->isExpanded()) {
|
if (!$this->isExpanded()) {
|
||||||
$classes .= " unexpanded";
|
$classes .= " unexpanded";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set jstree open state, or mark it as a leaf (closed) if there are no children
|
// Set jstree open state, or mark it as a leaf (closed) if there are no children
|
||||||
if(!$this->owner->$numChildrenMethod()) {
|
if (!$this->owner->$numChildrenMethod()) {
|
||||||
$classes .= " jstree-leaf closed";
|
$classes .= " jstree-leaf closed";
|
||||||
} elseif($this->isTreeOpened()) {
|
} elseif ($this->isTreeOpened()) {
|
||||||
$classes .= " jstree-open";
|
$classes .= " jstree-open";
|
||||||
} else {
|
} else {
|
||||||
$classes .= " jstree-closed closed";
|
$classes .= " jstree-closed closed";
|
||||||
}
|
}
|
||||||
return $classes;
|
return $classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the children of the DataObject with the given ID.
|
* Mark the children of the DataObject with the given ID.
|
||||||
*
|
*
|
||||||
* @param int $id ID of parent node
|
* @param int $id ID of parent node
|
||||||
* @param bool $open If this is true, mark the parent node as opened
|
* @param bool $open If this is true, mark the parent node as opened
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function markById($id, $open = false)
|
public function markById($id, $open = false)
|
||||||
{
|
{
|
||||||
if(isset($this->markedNodes[$id])) {
|
if (isset($this->markedNodes[$id])) {
|
||||||
$this->markChildren($this->markedNodes[$id]);
|
$this->markChildren($this->markedNodes[$id]);
|
||||||
if($open) {
|
if ($open) {
|
||||||
$this->markedNodes[$id]->markOpened();
|
$this->markedNodes[$id]->markOpened();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose the given object in the tree, by marking this page and all it ancestors.
|
* Expose the given object in the tree, by marking this page and all it ancestors.
|
||||||
*
|
*
|
||||||
* @param DataObject $childObj
|
* @param DataObject $childObj
|
||||||
*/
|
*/
|
||||||
public function markToExpose($childObj)
|
public function markToExpose($childObj)
|
||||||
{
|
{
|
||||||
if(is_object($childObj)){
|
if (is_object($childObj)) {
|
||||||
$stack = array_reverse($childObj->parentStack());
|
$stack = array_reverse($childObj->parentStack());
|
||||||
foreach($stack as $stackItem) {
|
foreach ($stack as $stackItem) {
|
||||||
$this->markById($stackItem->ID, true);
|
$this->markById($stackItem->ID, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the IDs of all the marked nodes.
|
* Return the IDs of all the marked nodes.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function markedNodeIDs()
|
public function markedNodeIDs()
|
||||||
{
|
{
|
||||||
return array_keys($this->markedNodes);
|
return array_keys($this->markedNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of this page and its ancestors, ordered item -> root.
|
* Return an array of this page and its ancestors, ordered item -> root.
|
||||||
*
|
*
|
||||||
* @return SiteTree[]
|
* @return SiteTree[]
|
||||||
*/
|
*/
|
||||||
public function parentStack()
|
public function parentStack()
|
||||||
{
|
{
|
||||||
$p = $this->owner;
|
$p = $this->owner;
|
||||||
|
|
||||||
while($p) {
|
while ($p) {
|
||||||
$stack[] = $p;
|
$stack[] = $p;
|
||||||
$p = $p->ParentID ? $p->Parent() : null;
|
$p = $p->ParentID ? $p->Parent() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $stack;
|
return $stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache of DataObjects' marked statuses: [ClassName][ID] = bool
|
* Cache of DataObjects' marked statuses: [ClassName][ID] = bool
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected static $marked = array();
|
protected static $marked = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache of DataObjects' expanded statuses: [ClassName][ID] = bool
|
* Cache of DataObjects' expanded statuses: [ClassName][ID] = bool
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected static $expanded = array();
|
protected static $expanded = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache of DataObjects' opened statuses: [ClassName][ID] = bool
|
* Cache of DataObjects' opened statuses: [ClassName][ID] = bool
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected static $treeOpened = array();
|
protected static $treeOpened = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this DataObject as expanded.
|
* Mark this DataObject as expanded.
|
||||||
*/
|
*/
|
||||||
public function markExpanded()
|
public function markExpanded()
|
||||||
{
|
{
|
||||||
self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
|
self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
|
||||||
self::$expanded[$this->owner->baseClass()][$this->owner->ID] = true;
|
self::$expanded[$this->owner->baseClass()][$this->owner->ID] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this DataObject as unexpanded.
|
* Mark this DataObject as unexpanded.
|
||||||
*/
|
*/
|
||||||
public function markUnexpanded()
|
public function markUnexpanded()
|
||||||
{
|
{
|
||||||
self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
|
self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
|
||||||
self::$expanded[$this->owner->baseClass()][$this->owner->ID] = false;
|
self::$expanded[$this->owner->baseClass()][$this->owner->ID] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this DataObject's tree as opened.
|
* Mark this DataObject's tree as opened.
|
||||||
*/
|
*/
|
||||||
public function markOpened()
|
public function markOpened()
|
||||||
{
|
{
|
||||||
self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
|
self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
|
||||||
self::$treeOpened[$this->owner->baseClass()][$this->owner->ID] = true;
|
self::$treeOpened[$this->owner->baseClass()][$this->owner->ID] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this DataObject's tree as closed.
|
* Mark this DataObject's tree as closed.
|
||||||
*/
|
*/
|
||||||
public function markClosed()
|
public function markClosed()
|
||||||
{
|
{
|
||||||
if(isset(self::$treeOpened[$this->owner->baseClass()][$this->owner->ID])) {
|
if (isset(self::$treeOpened[$this->owner->baseClass()][$this->owner->ID])) {
|
||||||
unset(self::$treeOpened[$this->owner->baseClass()][$this->owner->ID]);
|
unset(self::$treeOpened[$this->owner->baseClass()][$this->owner->ID]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this DataObject is marked.
|
* Check if this DataObject is marked.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isMarked()
|
public function isMarked()
|
||||||
{
|
{
|
||||||
$baseClass = $this->owner->baseClass();
|
$baseClass = $this->owner->baseClass();
|
||||||
$id = $this->owner->ID;
|
$id = $this->owner->ID;
|
||||||
return isset(self::$marked[$baseClass][$id]) ? self::$marked[$baseClass][$id] : false;
|
return isset(self::$marked[$baseClass][$id]) ? self::$marked[$baseClass][$id] : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this DataObject is expanded.
|
* Check if this DataObject is expanded.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isExpanded()
|
public function isExpanded()
|
||||||
{
|
{
|
||||||
$baseClass = $this->owner->baseClass();
|
$baseClass = $this->owner->baseClass();
|
||||||
$id = $this->owner->ID;
|
$id = $this->owner->ID;
|
||||||
return isset(self::$expanded[$baseClass][$id]) ? self::$expanded[$baseClass][$id] : false;
|
return isset(self::$expanded[$baseClass][$id]) ? self::$expanded[$baseClass][$id] : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this DataObject's tree is opened.
|
* Check if this DataObject's tree is opened.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isTreeOpened()
|
public function isTreeOpened()
|
||||||
{
|
{
|
||||||
$baseClass = $this->owner->baseClass();
|
$baseClass = $this->owner->baseClass();
|
||||||
$id = $this->owner->ID;
|
$id = $this->owner->ID;
|
||||||
return isset(self::$treeOpened[$baseClass][$id]) ? self::$treeOpened[$baseClass][$id] : false;
|
return isset(self::$treeOpened[$baseClass][$id]) ? self::$treeOpened[$baseClass][$id] : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of this DataObject's and all it's descendants IDs.
|
* Get a list of this DataObject's and all it's descendants IDs.
|
||||||
*
|
*
|
||||||
* @return int[]
|
* @return int[]
|
||||||
*/
|
*/
|
||||||
public function getDescendantIDList()
|
public function getDescendantIDList()
|
||||||
{
|
{
|
||||||
$idList = array();
|
$idList = array();
|
||||||
$this->loadDescendantIDListInto($idList);
|
$this->loadDescendantIDListInto($idList);
|
||||||
return $idList;
|
return $idList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of this DataObject's and all it's descendants ID, and put them in $idList.
|
* Get a list of this DataObject's and all it's descendants ID, and put them in $idList.
|
||||||
*
|
*
|
||||||
* @param array $idList Array to put results in.
|
* @param array $idList Array to put results in.
|
||||||
*/
|
*/
|
||||||
public function loadDescendantIDListInto(&$idList)
|
public function loadDescendantIDListInto(&$idList)
|
||||||
{
|
{
|
||||||
if($children = $this->AllChildren()) {
|
if ($children = $this->AllChildren()) {
|
||||||
foreach($children as $child) {
|
foreach ($children as $child) {
|
||||||
if(in_array($child->ID, $idList)) {
|
if (in_array($child->ID, $idList)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$idList[] = $child->ID;
|
$idList[] = $child->ID;
|
||||||
/** @var Hierarchy $ext */
|
/** @var Hierarchy $ext */
|
||||||
$ext = $child->getExtensionInstance('SilverStripe\ORM\Hierarchy\Hierarchy');
|
$ext = $child->getExtensionInstance('SilverStripe\ORM\Hierarchy\Hierarchy');
|
||||||
$ext->setOwner($child);
|
$ext->setOwner($child);
|
||||||
$ext->loadDescendantIDListInto($idList);
|
$ext->loadDescendantIDListInto($idList);
|
||||||
$ext->clearOwner();
|
$ext->clearOwner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the children for this DataObject.
|
* Get the children for this DataObject.
|
||||||
*
|
*
|
||||||
* @return DataList
|
* @return DataList
|
||||||
*/
|
*/
|
||||||
public function Children()
|
public function Children()
|
||||||
{
|
{
|
||||||
if(!(isset($this->_cache_children) && $this->_cache_children)) {
|
if (!(isset($this->_cache_children) && $this->_cache_children)) {
|
||||||
$result = $this->owner->stageChildren(false);
|
$result = $this->owner->stageChildren(false);
|
||||||
$children = array();
|
$children = array();
|
||||||
foreach ($result as $record) {
|
foreach ($result as $record) {
|
||||||
if ($record->canView()) {
|
if ($record->canView()) {
|
||||||
$children[] = $record;
|
$children[] = $record;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->_cache_children = new ArrayList($children);
|
$this->_cache_children = new ArrayList($children);
|
||||||
}
|
}
|
||||||
return $this->_cache_children;
|
return $this->_cache_children;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all children, including those 'not in menus'.
|
* Return all children, including those 'not in menus'.
|
||||||
*
|
*
|
||||||
* @return DataList
|
* @return DataList
|
||||||
*/
|
*/
|
||||||
public function AllChildren()
|
public function AllChildren()
|
||||||
{
|
{
|
||||||
return $this->owner->stageChildren(true);
|
return $this->owner->stageChildren(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all children, including those that have been deleted but are still in live.
|
* Return all children, including those that have been deleted but are still in live.
|
||||||
* - Deleted children will be marked as "DeletedFromStage"
|
* - Deleted children will be marked as "DeletedFromStage"
|
||||||
* - Added children will be marked as "AddedToStage"
|
* - Added children will be marked as "AddedToStage"
|
||||||
* - Modified children will be marked as "ModifiedOnStage"
|
* - Modified children will be marked as "ModifiedOnStage"
|
||||||
* - Everything else has "SameOnStage" set, as an indicator that this information has been looked up.
|
* - Everything else has "SameOnStage" set, as an indicator that this information has been looked up.
|
||||||
*
|
*
|
||||||
* @param mixed $context
|
* @param mixed $context
|
||||||
* @return ArrayList
|
* @return ArrayList
|
||||||
*/
|
*/
|
||||||
public function AllChildrenIncludingDeleted($context = null)
|
public function AllChildrenIncludingDeleted($context = null)
|
||||||
{
|
{
|
||||||
return $this->doAllChildrenIncludingDeleted($context);
|
return $this->doAllChildrenIncludingDeleted($context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AllChildrenIncludingDeleted
|
* @see AllChildrenIncludingDeleted
|
||||||
*
|
*
|
||||||
* @param mixed $context
|
* @param mixed $context
|
||||||
* @return ArrayList
|
* @return ArrayList
|
||||||
*/
|
*/
|
||||||
public function doAllChildrenIncludingDeleted($context = null)
|
public function doAllChildrenIncludingDeleted($context = null)
|
||||||
{
|
{
|
||||||
if (!$this->owner) {
|
if (!$this->owner) {
|
||||||
user_error('Hierarchy::doAllChildrenIncludingDeleted() called without $this->owner');
|
user_error('Hierarchy::doAllChildrenIncludingDeleted() called without $this->owner');
|
||||||
}
|
}
|
||||||
|
|
||||||
$baseClass = $this->owner->baseClass();
|
$baseClass = $this->owner->baseClass();
|
||||||
if($baseClass) {
|
if ($baseClass) {
|
||||||
$stageChildren = $this->owner->stageChildren(true);
|
$stageChildren = $this->owner->stageChildren(true);
|
||||||
|
|
||||||
// Add live site content that doesn't exist on the stage site, if required.
|
// Add live site content that doesn't exist on the stage site, if required.
|
||||||
if($this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
|
if ($this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
|
||||||
// Next, go through the live children. Only some of these will be listed
|
// Next, go through the live children. Only some of these will be listed
|
||||||
$liveChildren = $this->owner->liveChildren(true, true);
|
$liveChildren = $this->owner->liveChildren(true, true);
|
||||||
if($liveChildren) {
|
if ($liveChildren) {
|
||||||
$merged = new ArrayList();
|
$merged = new ArrayList();
|
||||||
$merged->merge($stageChildren);
|
$merged->merge($stageChildren);
|
||||||
$merged->merge($liveChildren);
|
$merged->merge($liveChildren);
|
||||||
$stageChildren = $merged;
|
$stageChildren = $merged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context);
|
$this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context);
|
||||||
} else {
|
} else {
|
||||||
user_error(
|
user_error(
|
||||||
"Hierarchy::AllChildren() Couldn't determine base class for '{$this->owner->class}'",
|
"Hierarchy::AllChildren() Couldn't determine base class for '{$this->owner->class}'",
|
||||||
E_USER_ERROR
|
E_USER_ERROR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $stageChildren;
|
return $stageChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the children that this page had, including pages that were deleted from both stage & live.
|
* Return all the children that this page had, including pages that were deleted from both stage & live.
|
||||||
*
|
*
|
||||||
* @return DataList
|
* @return DataList
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function AllHistoricalChildren()
|
public function AllHistoricalChildren()
|
||||||
{
|
{
|
||||||
if(!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
|
if (!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
|
||||||
throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
|
throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
|
||||||
}
|
}
|
||||||
|
|
||||||
$baseTable = $this->owner->baseTable();
|
$baseTable = $this->owner->baseTable();
|
||||||
$parentIDColumn = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ParentID');
|
$parentIDColumn = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ParentID');
|
||||||
return Versioned::get_including_deleted(
|
return Versioned::get_including_deleted(
|
||||||
$this->owner->baseClass(),
|
$this->owner->baseClass(),
|
||||||
[ $parentIDColumn => $this->owner->ID ],
|
[ $parentIDColumn => $this->owner->ID ],
|
||||||
"\"{$baseTable}\".\"ID\" ASC"
|
"\"{$baseTable}\".\"ID\" ASC"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the number of children that this page ever had, including pages that were deleted.
|
* Return the number of children that this page ever had, including pages that were deleted.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function numHistoricalChildren()
|
public function numHistoricalChildren()
|
||||||
{
|
{
|
||||||
if(!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
|
if (!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
|
||||||
throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
|
throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->AllHistoricalChildren()->count();
|
return $this->AllHistoricalChildren()->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the number of direct children. By default, values are cached after the first invocation. Can be
|
* Return the number of direct children. By default, values are cached after the first invocation. Can be
|
||||||
* augumented by {@link augmentNumChildrenCountQuery()}.
|
* augumented by {@link augmentNumChildrenCountQuery()}.
|
||||||
*
|
*
|
||||||
* @param bool $cache Whether to retrieve values from cache
|
* @param bool $cache Whether to retrieve values from cache
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function numChildren($cache = true)
|
public function numChildren($cache = true)
|
||||||
{
|
{
|
||||||
// Build the cache for this class if it doesn't exist.
|
// Build the cache for this class if it doesn't exist.
|
||||||
if(!$cache || !is_numeric($this->_cache_numChildren)) {
|
if (!$cache || !is_numeric($this->_cache_numChildren)) {
|
||||||
// Hey, this is efficient now!
|
// Hey, this is efficient now!
|
||||||
// We call stageChildren(), because Children() has canView() filtering
|
// We call stageChildren(), because Children() has canView() filtering
|
||||||
$this->_cache_numChildren = (int)$this->owner->stageChildren(true)->Count();
|
$this->_cache_numChildren = (int)$this->owner->stageChildren(true)->Count();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If theres no value in the cache, it just means that it doesn't have any children.
|
// If theres no value in the cache, it just means that it doesn't have any children.
|
||||||
return $this->_cache_numChildren;
|
return $this->_cache_numChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if we're on a controller where we should filter. ie. Are we loading the SiteTree?
|
* Checks if we're on a controller where we should filter. ie. Are we loading the SiteTree?
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function showingCMSTree()
|
public function showingCMSTree()
|
||||||
{
|
{
|
||||||
if (!Controller::has_curr()) {
|
if (!Controller::has_curr()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$controller = Controller::curr();
|
$controller = Controller::curr();
|
||||||
return $controller instanceof LeftAndMain
|
return $controller instanceof LeftAndMain
|
||||||
&& in_array($controller->getAction(), array("treeview", "listview", "getsubtree"));
|
&& in_array($controller->getAction(), array("treeview", "listview", "getsubtree"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return children in the stage site.
|
* Return children in the stage site.
|
||||||
*
|
*
|
||||||
* @param bool $showAll Include all of the elements, even those not shown in the menus. Only applicable when
|
* @param bool $showAll Include all of the elements, even those not shown in the menus. Only applicable when
|
||||||
* extension is applied to {@link SiteTree}.
|
* extension is applied to {@link SiteTree}.
|
||||||
* @return DataList
|
* @return DataList
|
||||||
*/
|
*/
|
||||||
public function stageChildren($showAll = false)
|
public function stageChildren($showAll = false)
|
||||||
{
|
{
|
||||||
$baseClass = $this->owner->baseClass();
|
$baseClass = $this->owner->baseClass();
|
||||||
$hide_from_hierarchy = $this->owner->config()->hide_from_hierarchy;
|
$hide_from_hierarchy = $this->owner->config()->hide_from_hierarchy;
|
||||||
$hide_from_cms_tree = $this->owner->config()->hide_from_cms_tree;
|
$hide_from_cms_tree = $this->owner->config()->hide_from_cms_tree;
|
||||||
$staged = $baseClass::get()
|
$staged = $baseClass::get()
|
||||||
->filter('ParentID', (int)$this->owner->ID)
|
->filter('ParentID', (int)$this->owner->ID)
|
||||||
->exclude('ID', (int)$this->owner->ID);
|
->exclude('ID', (int)$this->owner->ID);
|
||||||
if ($hide_from_hierarchy) {
|
if ($hide_from_hierarchy) {
|
||||||
$staged = $staged->exclude('ClassName', $hide_from_hierarchy);
|
$staged = $staged->exclude('ClassName', $hide_from_hierarchy);
|
||||||
}
|
}
|
||||||
if ($hide_from_cms_tree && $this->showingCMSTree()) {
|
if ($hide_from_cms_tree && $this->showingCMSTree()) {
|
||||||
$staged = $staged->exclude('ClassName', $hide_from_cms_tree);
|
$staged = $staged->exclude('ClassName', $hide_from_cms_tree);
|
||||||
}
|
}
|
||||||
if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
|
if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
|
||||||
$staged = $staged->filter('ShowInMenus', 1);
|
$staged = $staged->filter('ShowInMenus', 1);
|
||||||
}
|
}
|
||||||
$this->owner->extend("augmentStageChildren", $staged, $showAll);
|
$this->owner->extend("augmentStageChildren", $staged, $showAll);
|
||||||
return $staged;
|
return $staged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return children in the live site, if it exists.
|
* Return children in the live site, if it exists.
|
||||||
*
|
*
|
||||||
* @param bool $showAll Include all of the elements, even those not shown in the menus. Only
|
* @param bool $showAll Include all of the elements, even those not shown in the menus. Only
|
||||||
* applicable when extension is applied to {@link SiteTree}.
|
* applicable when extension is applied to {@link SiteTree}.
|
||||||
* @param bool $onlyDeletedFromStage Only return items that have been deleted from stage
|
* @param bool $onlyDeletedFromStage Only return items that have been deleted from stage
|
||||||
* @return DataList
|
* @return DataList
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function liveChildren($showAll = false, $onlyDeletedFromStage = false)
|
public function liveChildren($showAll = false, $onlyDeletedFromStage = false)
|
||||||
{
|
{
|
||||||
if(!$this->owner->hasExtension(Versioned::class)) {
|
if (!$this->owner->hasExtension(Versioned::class)) {
|
||||||
throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied');
|
throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied');
|
||||||
}
|
}
|
||||||
|
|
||||||
$baseClass = $this->owner->baseClass();
|
$baseClass = $this->owner->baseClass();
|
||||||
$hide_from_hierarchy = $this->owner->config()->hide_from_hierarchy;
|
$hide_from_hierarchy = $this->owner->config()->hide_from_hierarchy;
|
||||||
$hide_from_cms_tree = $this->owner->config()->hide_from_cms_tree;
|
$hide_from_cms_tree = $this->owner->config()->hide_from_cms_tree;
|
||||||
$children = $baseClass::get()
|
$children = $baseClass::get()
|
||||||
->filter('ParentID', (int)$this->owner->ID)
|
->filter('ParentID', (int)$this->owner->ID)
|
||||||
->exclude('ID', (int)$this->owner->ID)
|
->exclude('ID', (int)$this->owner->ID)
|
||||||
->setDataQueryParam(array(
|
->setDataQueryParam(array(
|
||||||
'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage',
|
'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage',
|
||||||
'Versioned.stage' => 'Live'
|
'Versioned.stage' => 'Live'
|
||||||
));
|
));
|
||||||
if ($hide_from_hierarchy) {
|
if ($hide_from_hierarchy) {
|
||||||
$children = $children->exclude('ClassName', $hide_from_hierarchy);
|
$children = $children->exclude('ClassName', $hide_from_hierarchy);
|
||||||
}
|
}
|
||||||
if ($hide_from_cms_tree && $this->showingCMSTree()) {
|
if ($hide_from_cms_tree && $this->showingCMSTree()) {
|
||||||
$children = $children->exclude('ClassName', $hide_from_cms_tree);
|
$children = $children->exclude('ClassName', $hide_from_cms_tree);
|
||||||
}
|
}
|
||||||
if(!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
|
if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
|
||||||
$children = $children->filter('ShowInMenus', 1);
|
$children = $children->filter('ShowInMenus', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $children;
|
return $children;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get this object's parent, optionally filtered by an SQL clause. If the clause doesn't match the parent, nothing
|
* Get this object's parent, optionally filtered by an SQL clause. If the clause doesn't match the parent, nothing
|
||||||
* is returned.
|
* is returned.
|
||||||
*
|
*
|
||||||
* @param string $filter
|
* @param string $filter
|
||||||
* @return DataObject
|
* @return DataObject
|
||||||
*/
|
*/
|
||||||
public function getParent($filter = null)
|
public function getParent($filter = null)
|
||||||
{
|
{
|
||||||
$parentID = $this->owner->ParentID;
|
$parentID = $this->owner->ParentID;
|
||||||
if(empty($parentID)) {
|
if (empty($parentID)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$idSQL = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ID');
|
$idSQL = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ID');
|
||||||
return DataObject::get_one($this->owner->class, array(
|
return DataObject::get_one($this->owner->class, array(
|
||||||
array($idSQL => $parentID),
|
array($idSQL => $parentID),
|
||||||
$filter
|
$filter
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the parents of this class in a set ordered from the lowest to highest parent.
|
* Return all the parents of this class in a set ordered from the lowest to highest parent.
|
||||||
*
|
*
|
||||||
* @return ArrayList
|
* @return ArrayList
|
||||||
*/
|
*/
|
||||||
public function getAncestors()
|
public function getAncestors()
|
||||||
{
|
{
|
||||||
$ancestors = new ArrayList();
|
$ancestors = new ArrayList();
|
||||||
$object = $this->owner;
|
$object = $this->owner;
|
||||||
|
|
||||||
while($object = $object->getParent()) {
|
while ($object = $object->getParent()) {
|
||||||
$ancestors->push($object);
|
$ancestors->push($object);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ancestors;
|
return $ancestors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a human-readable, flattened representation of the path to the object, using its {@link Title} attribute.
|
* Returns a human-readable, flattened representation of the path to the object, using its {@link Title} attribute.
|
||||||
*
|
*
|
||||||
* @param string $separator
|
* @param string $separator
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getBreadcrumbs($separator = ' » ')
|
public function getBreadcrumbs($separator = ' » ')
|
||||||
{
|
{
|
||||||
$crumbs = array();
|
$crumbs = array();
|
||||||
$ancestors = array_reverse($this->owner->getAncestors()->toArray());
|
$ancestors = array_reverse($this->owner->getAncestors()->toArray());
|
||||||
foreach ($ancestors as $ancestor) {
|
foreach ($ancestors as $ancestor) {
|
||||||
$crumbs[] = $ancestor->Title;
|
$crumbs[] = $ancestor->Title;
|
||||||
}
|
}
|
||||||
$crumbs[] = $this->owner->Title;
|
$crumbs[] = $this->owner->Title;
|
||||||
return implode($separator, $crumbs);
|
return implode($separator, $crumbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the next node in the tree of the type. If there is no instance of the className descended from this node,
|
* Get the next node in the tree of the type. If there is no instance of the className descended from this node,
|
||||||
* then search the parents.
|
* then search the parents.
|
||||||
*
|
*
|
||||||
* @todo Write!
|
* @todo Write!
|
||||||
*
|
*
|
||||||
* @param string $className Class name of the node to find
|
* @param string $className Class name of the node to find
|
||||||
* @param DataObject $afterNode Used for recursive calls to this function
|
* @param DataObject $afterNode Used for recursive calls to this function
|
||||||
* @return DataObject
|
* @return DataObject
|
||||||
*/
|
*/
|
||||||
public function naturalPrev($className, $afterNode = null)
|
public function naturalPrev($className, $afterNode = null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the next node in the tree of the type. If there is no instance of the className descended from this node,
|
* Get the next node in the tree of the type. If there is no instance of the className descended from this node,
|
||||||
* then search the parents.
|
* then search the parents.
|
||||||
* @param string $className Class name of the node to find.
|
* @param string $className Class name of the node to find.
|
||||||
* @param string|int $root ID/ClassName of the node to limit the search to
|
* @param string|int $root ID/ClassName of the node to limit the search to
|
||||||
* @param DataObject $afterNode Used for recursive calls to this function
|
* @param DataObject $afterNode Used for recursive calls to this function
|
||||||
* @return DataObject
|
* @return DataObject
|
||||||
*/
|
*/
|
||||||
public function naturalNext($className = null, $root = 0, $afterNode = null)
|
public function naturalNext($className = null, $root = 0, $afterNode = null)
|
||||||
{
|
{
|
||||||
// If this node is not the node we are searching from, then we can possibly return this node as a solution
|
// If this node is not the node we are searching from, then we can possibly return this node as a solution
|
||||||
if($afterNode && $afterNode->ID != $this->owner->ID) {
|
if ($afterNode && $afterNode->ID != $this->owner->ID) {
|
||||||
if(!$className || ($className && $this->owner->class == $className)) {
|
if (!$className || ($className && $this->owner->class == $className)) {
|
||||||
return $this->owner;
|
return $this->owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$nextNode = null;
|
$nextNode = null;
|
||||||
$baseClass = $this->owner->baseClass();
|
$baseClass = $this->owner->baseClass();
|
||||||
|
|
||||||
$children = $baseClass::get()
|
$children = $baseClass::get()
|
||||||
->filter('ParentID', (int)$this->owner->ID)
|
->filter('ParentID', (int)$this->owner->ID)
|
||||||
->sort('"Sort"', 'ASC');
|
->sort('"Sort"', 'ASC');
|
||||||
if ($afterNode) {
|
if ($afterNode) {
|
||||||
$children = $children->filter('Sort:GreaterThan', $afterNode->Sort);
|
$children = $children->filter('Sort:GreaterThan', $afterNode->Sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try all the siblings of this node after the given node
|
// Try all the siblings of this node after the given node
|
||||||
/*if( $siblings = DataObject::get( $this->owner->baseClass(),
|
/*if( $siblings = DataObject::get( $this->owner->baseClass(),
|
||||||
"\"ParentID\"={$this->owner->ParentID}" . ( $afterNode ) ? "\"Sort\"
|
"\"ParentID\"={$this->owner->ParentID}" . ( $afterNode ) ? "\"Sort\"
|
||||||
> {$afterNode->Sort}" : "" , '\"Sort\" ASC' ) ) $searchNodes->merge( $siblings );*/
|
> {$afterNode->Sort}" : "" , '\"Sort\" ASC' ) ) $searchNodes->merge( $siblings );*/
|
||||||
|
|
||||||
if($children) {
|
if ($children) {
|
||||||
foreach($children as $node) {
|
foreach ($children as $node) {
|
||||||
if($nextNode = $node->naturalNext($className, $node->ID, $this->owner)) {
|
if ($nextNode = $node->naturalNext($className, $node->ID, $this->owner)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($nextNode) {
|
if ($nextNode) {
|
||||||
return $nextNode;
|
return $nextNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is not an instance of the root class or has the root id, search the parent
|
// if this is not an instance of the root class or has the root id, search the parent
|
||||||
if(!(is_numeric($root) && $root == $this->owner->ID || $root == $this->owner->class)
|
if (!(is_numeric($root) && $root == $this->owner->ID || $root == $this->owner->class)
|
||||||
&& ($parent = $this->owner->Parent())) {
|
&& ($parent = $this->owner->Parent())) {
|
||||||
return $parent->naturalNext( $className, $root, $this->owner );
|
return $parent->naturalNext($className, $root, $this->owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush all Hierarchy caches:
|
* Flush all Hierarchy caches:
|
||||||
* - Children (instance)
|
* - Children (instance)
|
||||||
* - NumChildren (instance)
|
* - NumChildren (instance)
|
||||||
* - Marked (global)
|
* - Marked (global)
|
||||||
* - Expanded (global)
|
* - Expanded (global)
|
||||||
* - TreeOpened (global)
|
* - TreeOpened (global)
|
||||||
*/
|
*/
|
||||||
public function flushCache()
|
public function flushCache()
|
||||||
{
|
{
|
||||||
$this->_cache_children = null;
|
$this->_cache_children = null;
|
||||||
$this->_cache_numChildren = null;
|
$this->_cache_numChildren = null;
|
||||||
self::$marked = array();
|
self::$marked = array();
|
||||||
self::$expanded = array();
|
self::$expanded = array();
|
||||||
self::$treeOpened = array();
|
self::$treeOpened = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset global Hierarchy caches:
|
* Reset global Hierarchy caches:
|
||||||
* - Marked
|
* - Marked
|
||||||
* - Expanded
|
* - Expanded
|
||||||
* - TreeOpened
|
* - TreeOpened
|
||||||
*/
|
*/
|
||||||
public static function reset()
|
public static function reset()
|
||||||
{
|
{
|
||||||
self::$marked = array();
|
self::$marked = array();
|
||||||
self::$expanded = array();
|
self::$expanded = array();
|
||||||
self::$treeOpened = array();
|
self::$treeOpened = array();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
namespace SilverStripe\ORM;
|
namespace SilverStripe\ORM;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown by {@link DataObject}::write if validation fails. By throwing an
|
* Exception thrown by {@link DataObject}::write if validation fails. By throwing an
|
||||||
@ -11,73 +13,62 @@ use Exception;
|
|||||||
*/
|
*/
|
||||||
class ValidationException extends Exception
|
class ValidationException extends Exception
|
||||||
{
|
{
|
||||||
|
use Injectable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The contained ValidationResult related to this error
|
* The contained ValidationResult related to this error
|
||||||
*
|
*
|
||||||
* @var ValidationResult
|
* @var ValidationResult
|
||||||
*/
|
*/
|
||||||
protected $result;
|
protected $result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new ValidationException with an optional ValidationResult object
|
* Construct a new ValidationException with an optional ValidationResult object
|
||||||
*
|
*
|
||||||
* @param ValidationResult|string $result The ValidationResult containing the
|
* @param ValidationResult|string $result The ValidationResult containing the
|
||||||
* failed result. Can be substituted with an error message instead if no
|
* failed result, or error message to build error from
|
||||||
* ValidationResult exists.
|
* @param integer $code The error code number
|
||||||
* @param string|integer $message The error message. If $result was given the
|
*/
|
||||||
* message string rather than a ValidationResult object then this will have
|
public function __construct($result = null, $code = 0)
|
||||||
* the error code number.
|
{
|
||||||
* @param integer $code The error code number, if not given in the second parameter
|
// Catch legacy behaviour where second argument was not code
|
||||||
*/
|
if ($code && !is_numeric($code)) {
|
||||||
public function __construct($result = null, $code = 0, $dummy = null) {
|
throw new InvalidArgumentException("Code must be numeric");
|
||||||
$exceptionMessage = null;
|
}
|
||||||
|
|
||||||
// Backwards compatibiliy failover. The 2nd argument used to be $message, and $code the 3rd.
|
// Set default message and result
|
||||||
// For callers using that, we ditch the message
|
$exceptionMessage = _t("ValidationException.DEFAULT_ERROR", "Validation error");
|
||||||
if(!is_numeric($code)) {
|
if (!$result) {
|
||||||
$exceptionMessage = $code;
|
$result = $exceptionMessage;
|
||||||
if($dummy) $code = $dummy;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if($result instanceof ValidationResult) {
|
// Check result type
|
||||||
$this->result = $result;
|
if ($result instanceof ValidationResult) {
|
||||||
|
$this->result = $result;
|
||||||
|
// Pick first message
|
||||||
|
foreach ($result->getMessages() as $message) {
|
||||||
|
$exceptionMessage = $message['message'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} elseif (is_string($result)) {
|
||||||
|
$this->result = ValidationResult::create()->addError($result);
|
||||||
|
$exceptionMessage = $result;
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"ValidationExceptions must be passed a ValdiationResult, a string, or nothing at all"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} else if(is_string($result)) {
|
parent::__construct($exceptionMessage, $code);
|
||||||
$this->result = ValidationResult::create()->addError($result);
|
}
|
||||||
|
|
||||||
} else if(!$result) {
|
/**
|
||||||
$this->result = ValidationResult::create()->addError(_t("ValdiationExcetpion.DEFAULT_ERROR", "Validation error"));
|
* Retrieves the ValidationResult related to this error
|
||||||
|
*
|
||||||
} else {
|
* @return ValidationResult
|
||||||
throw new InvalidArgumentException(
|
*/
|
||||||
"ValidationExceptions must be passed a ValdiationResult, a string, or nothing at all");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct
|
|
||||||
parent::__construct($exceptionMessage ? $exceptionMessage : $this->result->message(), $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a ValidationException with a message for a single field-specific error message.
|
|
||||||
*
|
|
||||||
* @param string $field The field name
|
|
||||||
* @param string $message The error message
|
|
||||||
* @return ValidationException
|
|
||||||
*/
|
|
||||||
static function create_for_field($field, $message) {
|
|
||||||
$result = new ValidationResult;
|
|
||||||
$result->addFieldError($field, $message);
|
|
||||||
return new ValidationException($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the ValidationResult related to this error
|
|
||||||
*
|
|
||||||
* @return ValidationResult
|
|
||||||
*/
|
|
||||||
public function getResult()
|
public function getResult()
|
||||||
{
|
{
|
||||||
return $this->result;
|
return $this->result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,259 +2,235 @@
|
|||||||
|
|
||||||
namespace SilverStripe\ORM;
|
namespace SilverStripe\ORM;
|
||||||
|
|
||||||
use SilverStripe\Core\Object;
|
use InvalidArgumentException;
|
||||||
|
use Serializable;
|
||||||
|
use SilverStripe\Core\Convert;
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
|
use SilverStripe\Dev\Deprecation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that combined as a boolean result with an optional list of error messages.
|
* A class that combined as a boolean result with an optional list of error messages.
|
||||||
* This is used for returning validation results from validators
|
* This is used for returning validation results from validators
|
||||||
|
*
|
||||||
|
* Each message can have a code or field which will uniquely identify that message. However,
|
||||||
|
* messages can be stored without a field or message as an "overall" message.
|
||||||
*/
|
*/
|
||||||
class ValidationResult extends Object
|
class ValidationResult implements Serializable
|
||||||
{
|
{
|
||||||
/**
|
use Injectable;
|
||||||
* @var bool - is the result valid or not
|
|
||||||
*/
|
|
||||||
protected $isValid = true;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard "error" type
|
||||||
|
*/
|
||||||
|
const TYPE_ERROR = 'error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array of errors
|
* Standard "good" message type
|
||||||
*/
|
*/
|
||||||
protected $errorList = array();
|
const TYPE_GOOD = 'good';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new ValidationResult.
|
* Non-error message type.
|
||||||
* By default, it is a successful result. Call $this->error() to record errors.
|
*/
|
||||||
*
|
const TYPE_INFO = 'info';
|
||||||
* @param void $valid @deprecated
|
|
||||||
* @param void $message @deprecated
|
/**
|
||||||
*/
|
* Warning message type
|
||||||
public function __construct($valid = null, $message = null) {
|
*/
|
||||||
if ($message !== null) {
|
const TYPE_WARNING = 'warning';
|
||||||
Deprecation::notice('3.2', '$message parameter is deprecated please use addMessage or addError instead', false);
|
|
||||||
$this->addError($message);
|
/**
|
||||||
}
|
* Message type is html
|
||||||
if ($valid !== null) {
|
*/
|
||||||
Deprecation::notice('3.2', '$valid parameter is deprecated please addError to mark the result as invalid', false);
|
const CAST_HTML = 'html';
|
||||||
$this->isValid = $valid;
|
|
||||||
if ($message) {
|
/**
|
||||||
$this->errorList[] = $message;
|
* Message type is plain text
|
||||||
|
*/
|
||||||
|
const CAST_TEXT = 'text';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the result valid or not.
|
||||||
|
* Note that there can be non-error messages in the list.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $isValid = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of messages
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $messages = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ValidationResult.
|
||||||
|
* By default, it is a successful result. Call $this->error() to record errors.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
if (func_num_args() > 0) {
|
||||||
|
Deprecation::notice('3.2', '$valid parameter is deprecated please addError to mark the result as invalid', false);
|
||||||
|
$this->isValid = func_get_arg(0);
|
||||||
}
|
}
|
||||||
parent::__construct();
|
if (func_num_args() > 1) {
|
||||||
}
|
Deprecation::notice('3.2', '$message parameter is deprecated please use addMessage or addError instead', false);
|
||||||
|
$this->addError(func_get_arg(1));
|
||||||
/**
|
|
||||||
* Return the full error meta-data, suitable for combining with another ValidationResult.
|
|
||||||
*/
|
|
||||||
function getErrorMetaData() {
|
|
||||||
return $this->errorList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record a
|
|
||||||
* against this validation result.
|
|
||||||
*
|
|
||||||
* It's better to use addError, addFeildError, addMessage, or addFieldMessage instead.
|
|
||||||
*
|
|
||||||
* @param string $message The message string.
|
|
||||||
* @param string $code A codename for this error. Only one message per codename will be added.
|
|
||||||
* This can be usedful for ensuring no duplicate messages
|
|
||||||
* @param string $fieldName The field to link the message to. If omitted; a form-wide message is assumed.
|
|
||||||
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
|
||||||
* class to the form, so other values can be used if desired.
|
|
||||||
* @param bool $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
|
|
||||||
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
|
|
||||||
* user supplied data in the message.
|
|
||||||
*
|
|
||||||
* @deprecated 3.2
|
|
||||||
*/
|
|
||||||
public function error($message, $code = null, $fieldName = null, $messageType = "bad", $escapeHtml = true) {
|
|
||||||
Deprecation::notice('3.2', 'Use addError or addFieldError instead.');
|
|
||||||
|
|
||||||
return $this->addFieldError($fieldName, $message, $messageType, $code, $escapeHtml);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record an error against this validation result,
|
|
||||||
*
|
|
||||||
* @param string $message The message string.
|
|
||||||
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
|
||||||
* class to the form, so other values can be used if desired.
|
|
||||||
* @param string $code A codename for this error. Only one message per codename will be added.
|
|
||||||
* This can be usedful for ensuring no duplicate messages
|
|
||||||
* @param bool $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
|
|
||||||
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
|
|
||||||
* user supplied data in the message.
|
|
||||||
*/
|
|
||||||
public function addError($message, $messageType = "bad", $code = null, $escapeHtml = true) {
|
|
||||||
|
|
||||||
return $this->addFieldError(null, $message, $messageType, $code, $escapeHtml);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record an error against this validation result,
|
|
||||||
*
|
|
||||||
* @param string $fieldName The field to link the message to. If omitted; a form-wide message is assumed.
|
|
||||||
* @param string $message The message string.
|
|
||||||
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
|
||||||
* class to the form, so other values can be used if desired.
|
|
||||||
* @param string $code A codename for this error. Only one message per codename will be added.
|
|
||||||
* This can be usedful for ensuring no duplicate messages
|
|
||||||
* @param bool $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
|
|
||||||
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
|
|
||||||
* user supplied data in the message.
|
|
||||||
*/
|
|
||||||
public function addFieldError($fieldName = null, $message, $messageType = "bad", $code = null, $escapeHtml = true) {
|
|
||||||
$this->isValid = false;
|
|
||||||
|
|
||||||
return $this->addFieldMessage($fieldName, $message, $messageType, $code, $escapeHtml);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a message to this ValidationResult without necessarily marking it as an error
|
|
||||||
*
|
|
||||||
* @param string $message The message string.
|
|
||||||
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
|
||||||
* class to the form, so other values can be used if desired.
|
|
||||||
* @param string $code A codename for this error. Only one message per codename will be added.
|
|
||||||
* This can be usedful for ensuring no duplicate messages
|
|
||||||
* @param bool $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
|
|
||||||
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
|
|
||||||
* user supplied data in the message.
|
|
||||||
*/
|
|
||||||
public function addMessage($message, $messageType = "bad", $code = null, $escapeHtml = true) {
|
|
||||||
return $this->addFieldMessage(null, $message, $messageType, $code, $escapeHtml);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a message to this ValidationResult without necessarily marking it as an error
|
|
||||||
*
|
|
||||||
* @param string $fieldName The field to link the message to. If omitted; a form-wide message is assumed.
|
|
||||||
* @param string $message The message string.
|
|
||||||
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
|
||||||
* class to the form, so other values can be used if desired.
|
|
||||||
* @param string $code A codename for this error. Only one message per codename will be added.
|
|
||||||
* This can be usedful for ensuring no duplicate messages
|
|
||||||
* @param bool $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
|
|
||||||
* In that case, you might want to use {@link Convert::raw2xml()} to escape any
|
|
||||||
* user supplied data in the message.
|
|
||||||
*/
|
|
||||||
public function addFieldMessage($fieldName, $message, $messageType = "bad", $code = null, $escapeHtml = true) {
|
|
||||||
$metadata = array(
|
|
||||||
'message' => $escapeHtml ? Convert::raw2xml($message) : $message,
|
|
||||||
'fieldName' => $fieldName,
|
|
||||||
'messageType' => $messageType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if($code) {
|
|
||||||
if(!is_numeric($code)) {
|
|
||||||
$this->errorList[$code] = $metadata;
|
|
||||||
} else {
|
|
||||||
throw new InvalidArgumentException(
|
|
||||||
"ValidationResult::error() - Don't use a numeric code '$code'. Use a string.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->errorList[] = $metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the result is valid.
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function valid()
|
|
||||||
{
|
|
||||||
return $this->isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an array of errors
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function messageList()
|
|
||||||
{
|
|
||||||
$list = array();
|
|
||||||
foreach($this->errorList as $key => $item) {
|
|
||||||
if(is_numeric($key)) $list[] = $item['message'];
|
|
||||||
else $list[$key] = $item['message'];
|
|
||||||
}
|
|
||||||
return $list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the field-specific messages as a map.
|
|
||||||
* Keys will be field names, and values will be a 2 element map with keys 'messsage', and 'messageType'
|
|
||||||
*/
|
|
||||||
public function fieldErrors() {
|
|
||||||
$output = array();
|
|
||||||
foreach($this->errorList as $key => $item) {
|
|
||||||
if($item['fieldName']) {
|
|
||||||
$output[$item['fieldName']] = array(
|
|
||||||
'message' => $item['message'],
|
|
||||||
'messageType' => $item['messageType']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an array of error codes
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function codeList()
|
|
||||||
{
|
|
||||||
$codeList = array();
|
|
||||||
foreach ($this->errorList as $k => $v) {
|
|
||||||
if (!is_numeric($k)) {
|
|
||||||
$codeList[] = $k;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $codeList;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the error message as a string.
|
* Record an error against this validation result,
|
||||||
* @return string
|
*
|
||||||
*/
|
* @param string $message The message string.
|
||||||
public function message()
|
* @param string $messageType Passed as a CSS class to the form, so other values can be used if desired.
|
||||||
|
* Standard types are defined by the TYPE_ constant definitions.
|
||||||
|
* @param string $code A codename for this error. Only one message per codename will be added.
|
||||||
|
* This can be usedful for ensuring no duplicate messages
|
||||||
|
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||||
|
* Bool values will be treated as plain text flag.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addError($message, $messageType = self::TYPE_ERROR, $code = null, $cast = self::CAST_TEXT)
|
||||||
{
|
{
|
||||||
return implode("; ", $this->messageList());
|
return $this->addFieldError(null, $message, $messageType, $code, $cast);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The the error message that's not related to a field as a string
|
* Record an error against this validation result,
|
||||||
*/
|
*
|
||||||
public function overallMessage() {
|
* @param string $fieldName The field to link the message to. If omitted; a form-wide message is assumed.
|
||||||
$messages = array();
|
* @param string $message The message string.
|
||||||
foreach($this->errorList as $item) {
|
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
||||||
if(!$item['fieldName']) $messages[] = $item['message'];
|
* class to the form, so other values can be used if desired.
|
||||||
}
|
* @param string $code A codename for this error. Only one message per codename will be added.
|
||||||
return implode("; ", $messages);
|
* This can be usedful for ensuring no duplicate messages
|
||||||
}
|
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||||
|
* Bool values will be treated as plain text flag.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addFieldError(
|
||||||
|
$fieldName,
|
||||||
|
$message,
|
||||||
|
$messageType = self::TYPE_ERROR,
|
||||||
|
$code = null,
|
||||||
|
$cast = self::CAST_TEXT
|
||||||
|
) {
|
||||||
|
$this->isValid = false;
|
||||||
|
return $this->addFieldMessage($fieldName, $message, $messageType, $code, $cast);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a starred list of all messages
|
* Add a message to this ValidationResult without necessarily marking it as an error
|
||||||
* @return string
|
*
|
||||||
*/
|
* @param string $message The message string.
|
||||||
public function starredList()
|
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
||||||
|
* class to the form, so other values can be used if desired.
|
||||||
|
* @param string $code A codename for this error. Only one message per codename will be added.
|
||||||
|
* This can be usedful for ensuring no duplicate messages
|
||||||
|
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||||
|
* Bool values will be treated as plain text flag.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addMessage($message, $messageType = self::TYPE_ERROR, $code = null, $cast = self::CAST_TEXT)
|
||||||
{
|
{
|
||||||
return " * " . implode("\n * ", $this->messageList());
|
return $this->addFieldMessage(null, $message, $messageType, $code, $cast);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine this Validation Result with the ValidationResult given in other.
|
* Add a message to this ValidationResult without necessarily marking it as an error
|
||||||
* It will be valid if both this and the other result are valid.
|
*
|
||||||
* This object will be modified to contain the new validation information.
|
* @param string $fieldName The field to link the message to. If omitted; a form-wide message is assumed.
|
||||||
*
|
* @param string $message The message string.
|
||||||
* @param ValidationResult $other the validation result object to combine
|
* @param string $messageType The type of message: e.g. "bad", "warning", "good", or "required". Passed as a CSS
|
||||||
* @return $this
|
* class to the form, so other values can be used if desired.
|
||||||
*/
|
* @param string $code A codename for this error. Only one message per codename will be added.
|
||||||
|
* This can be usedful for ensuring no duplicate messages
|
||||||
|
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||||
|
* Bool values will be treated as plain text flag.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addFieldMessage(
|
||||||
|
$fieldName,
|
||||||
|
$message,
|
||||||
|
$messageType = self::TYPE_ERROR,
|
||||||
|
$code = null,
|
||||||
|
$cast = self::CAST_TEXT
|
||||||
|
) {
|
||||||
|
if ($code && is_numeric($code)) {
|
||||||
|
throw new InvalidArgumentException("Don't use a numeric code '$code'. Use a string.");
|
||||||
|
}
|
||||||
|
if (is_bool($cast)) {
|
||||||
|
$cast = $cast ? self::CAST_TEXT : self::CAST_HTML;
|
||||||
|
}
|
||||||
|
$metadata = array(
|
||||||
|
'message' => $message,
|
||||||
|
'fieldName' => $fieldName,
|
||||||
|
'messageType' => $messageType,
|
||||||
|
'messageCast' => $cast,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($code) {
|
||||||
|
$this->messages[$code] = $metadata;
|
||||||
|
} else {
|
||||||
|
$this->messages[] = $metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the result is valid.
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function isValid()
|
||||||
|
{
|
||||||
|
return $this->isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the full error meta-data, suitable for combining with another ValidationResult.
|
||||||
|
*
|
||||||
|
* @return array Array of messages, where each item is an array of data for that message.
|
||||||
|
*/
|
||||||
|
public function getMessages()
|
||||||
|
{
|
||||||
|
return $this->messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine this Validation Result with the ValidationResult given in other.
|
||||||
|
* It will be valid if both this and the other result are valid.
|
||||||
|
* This object will be modified to contain the new validation information.
|
||||||
|
*
|
||||||
|
* @param ValidationResult $other the validation result object to combine
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
public function combineAnd(ValidationResult $other)
|
public function combineAnd(ValidationResult $other)
|
||||||
{
|
{
|
||||||
$this->isValid = $this->isValid && $other->valid();
|
$this->isValid = $this->isValid && $other->isValid();
|
||||||
$this->errorList = array_merge($this->errorList, $other->getErrorMetaData());
|
$this->messages = array_merge($this->messages, $other->getMessages());
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation of object
|
||||||
|
*
|
||||||
|
* @return string the string representation of the object or null
|
||||||
|
*/
|
||||||
|
public function serialize()
|
||||||
|
{
|
||||||
|
return json_encode([$this->messages, $this->isValid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the object
|
||||||
|
*
|
||||||
|
* @param string $serialized
|
||||||
|
*/
|
||||||
|
public function unserialize($serialized)
|
||||||
|
{
|
||||||
|
list($this->messages, $this->isValid) = json_decode($serialized, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use SilverStripe\Forms\Form;
|
|||||||
use SilverStripe\Forms\FormAction;
|
use SilverStripe\Forms\FormAction;
|
||||||
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
|
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides versioned dataobject support to {@see GridFieldDetailForm_ItemRequest}
|
* Provides versioned dataobject support to {@see GridFieldDetailForm_ItemRequest}
|
||||||
@ -25,7 +25,7 @@ class VersionedGridFieldItemRequest extends GridFieldDetailForm_ItemRequest
|
|||||||
// Check if record is versionable
|
// Check if record is versionable
|
||||||
/** @var Versioned|DataObject $record */
|
/** @var Versioned|DataObject $record */
|
||||||
$record = $this->getRecord();
|
$record = $this->getRecord();
|
||||||
if (!$record || !$record->has_extension('SilverStripe\ORM\Versioning\Versioned')) {
|
if (!$record || !$record->has_extension(Versioned::class)) {
|
||||||
return $actions;
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,12 +100,7 @@ class VersionedGridFieldItemRequest extends GridFieldDetailForm_ItemRequest
|
|||||||
|
|
||||||
// Record name before it's deleted
|
// Record name before it's deleted
|
||||||
$title = $record->Title;
|
$title = $record->Title;
|
||||||
|
|
||||||
try {
|
|
||||||
$record->doArchive();
|
$record->doArchive();
|
||||||
} catch (ValidationException $e) {
|
|
||||||
return $this->generateValidationResponse($form, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = sprintf(
|
$message = sprintf(
|
||||||
_t('VersionedGridFieldItemRequest.Archived', 'Archived %s %s'),
|
_t('VersionedGridFieldItemRequest.Archived', 'Archived %s %s'),
|
||||||
@ -139,15 +134,9 @@ class VersionedGridFieldItemRequest extends GridFieldDetailForm_ItemRequest
|
|||||||
return $this->httpError(403);
|
return $this->httpError(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save from form data
|
|
||||||
try {
|
|
||||||
// Initial save and reload
|
// Initial save and reload
|
||||||
$record = $this->saveFormIntoRecord($data, $form);
|
$record = $this->saveFormIntoRecord($data, $form);
|
||||||
$record->publishRecursive();
|
$record->publishRecursive();
|
||||||
} catch (ValidationException $e) {
|
|
||||||
return $this->generateValidationResponse($form, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$editURL = $this->Link('edit');
|
$editURL = $this->Link('edit');
|
||||||
$xmlTitle = Convert::raw2xml($record->Title);
|
$xmlTitle = Convert::raw2xml($record->Title);
|
||||||
$link = "<a href=\"{$editURL}\">{$xmlTitle}</a>";
|
$link = "<a href=\"{$editURL}\">{$xmlTitle}</a>";
|
||||||
@ -181,12 +170,7 @@ class VersionedGridFieldItemRequest extends GridFieldDetailForm_ItemRequest
|
|||||||
|
|
||||||
// Record name before it's deleted
|
// Record name before it's deleted
|
||||||
$title = $record->Title;
|
$title = $record->Title;
|
||||||
|
|
||||||
try {
|
|
||||||
$record->doUnpublish();
|
$record->doUnpublish();
|
||||||
} catch (ValidationException $e) {
|
|
||||||
return $this->generateValidationResponse($form, $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = sprintf(
|
$message = sprintf(
|
||||||
_t('VersionedGridFieldItemRequest.Unpublished', 'Unpublished %s %s'),
|
_t('VersionedGridFieldItemRequest.Unpublished', 'Unpublished %s %s'),
|
||||||
@ -205,11 +189,12 @@ class VersionedGridFieldItemRequest extends GridFieldDetailForm_ItemRequest
|
|||||||
*/
|
*/
|
||||||
protected function setFormMessage($form, $message)
|
protected function setFormMessage($form, $message)
|
||||||
{
|
{
|
||||||
$form->sessionMessage($message, 'good', false);
|
$form->sessionMessage($message, 'good', ValidationResult::CAST_HTML);
|
||||||
$controller = $this->getToplevelController();
|
$controller = $this->getToplevelController();
|
||||||
if ($controller->hasMethod('getEditForm')) {
|
if ($controller->hasMethod('getEditForm')) {
|
||||||
|
/** @var Form $backForm */
|
||||||
$backForm = $controller->getEditForm();
|
$backForm = $controller->getEditForm();
|
||||||
$backForm->sessionMessage($message, 'good', false);
|
$backForm->sessionMessage($message, 'good', ValidationResult::CAST_HTML);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,12 +124,13 @@ class CMSMemberLoginForm extends LoginForm
|
|||||||
/**
|
/**
|
||||||
* Redirect the user to the change password form.
|
* Redirect the user to the change password form.
|
||||||
*
|
*
|
||||||
|
* @skipUpgrade
|
||||||
* @return HTTPResponse
|
* @return HTTPResponse
|
||||||
*/
|
*/
|
||||||
protected function redirectToChangePassword()
|
protected function redirectToChangePassword()
|
||||||
{
|
{
|
||||||
// Since this form is loaded via an iframe, this redirect must be performed via javascript
|
// Since this form is loaded via an iframe, this redirect must be performed via javascript
|
||||||
$changePasswordForm = new ChangePasswordForm($this->controller, 'SilverStripe\\Security\\ChangePasswordForm');
|
$changePasswordForm = new ChangePasswordForm($this->controller, 'ChangePasswordForm');
|
||||||
$changePasswordForm->sessionMessage(
|
$changePasswordForm->sessionMessage(
|
||||||
_t('Member.PASSWORDEXPIRED', 'Your password has expired. Please choose a new one.'),
|
_t('Member.PASSWORDEXPIRED', 'Your password has expired. Please choose a new one.'),
|
||||||
'good'
|
'good'
|
||||||
|
@ -14,6 +14,7 @@ use SilverStripe\Forms\PasswordField;
|
|||||||
use SilverStripe\Forms\FormAction;
|
use SilverStripe\Forms\FormAction;
|
||||||
use SilverStripe\Forms\HiddenField;
|
use SilverStripe\Forms\HiddenField;
|
||||||
use SilverStripe\Forms\Form;
|
use SilverStripe\Forms\Form;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard Change Password Form
|
* Standard Change Password Form
|
||||||
@ -64,7 +65,6 @@ class ChangePasswordForm extends Form
|
|||||||
parent::__construct($controller, $name, $fields, $actions);
|
parent::__construct($controller, $name, $fields, $actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the password
|
* Change the password
|
||||||
*
|
*
|
||||||
@ -75,7 +75,7 @@ class ChangePasswordForm extends Form
|
|||||||
{
|
{
|
||||||
if ($member = Member::currentUser()) {
|
if ($member = Member::currentUser()) {
|
||||||
// The user was logged in, check the current password
|
// The user was logged in, check the current password
|
||||||
if (empty($data['OldPassword']) || !$member->checkPassword($data['OldPassword'])->valid()) {
|
if (empty($data['OldPassword']) || !$member->checkPassword($data['OldPassword'])->isValid()) {
|
||||||
$this->clearMessage();
|
$this->clearMessage();
|
||||||
$this->sessionMessage(
|
$this->sessionMessage(
|
||||||
_t('Member.ERRORPASSWORDNOTMATCH', "Your current password does not match, please try again"),
|
_t('Member.ERRORPASSWORDNOTMATCH', "Your current password does not match, please try again"),
|
||||||
@ -108,60 +108,52 @@ class ChangePasswordForm extends Form
|
|||||||
|
|
||||||
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
|
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
|
||||||
return $this->controller->redirect($this->controller->Link('changepassword'));
|
return $this->controller->redirect($this->controller->Link('changepassword'));
|
||||||
} elseif ($data['NewPassword1'] == $data['NewPassword2']) {
|
}
|
||||||
$isValid = $member->changePassword($data['NewPassword1']);
|
|
||||||
if ($isValid->valid()) {
|
|
||||||
// Clear locked out status
|
|
||||||
$member->LockedOutUntil = null;
|
|
||||||
$member->FailedLoginCount = null;
|
|
||||||
$member->write();
|
|
||||||
|
|
||||||
if ($member->canLogIn()->valid()) {
|
// Fail if passwords do not match
|
||||||
$member->logIn();
|
if ($data['NewPassword1'] !== $data['NewPassword2']) {
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Add confirmation message to login redirect
|
|
||||||
Session::clear('AutoLoginHash');
|
|
||||||
|
|
||||||
if (!empty($_REQUEST['BackURL'])
|
|
||||||
// absolute redirection URLs may cause spoofing
|
|
||||||
&& Director::is_site_url($_REQUEST['BackURL'])
|
|
||||||
) {
|
|
||||||
$url = Director::absoluteURL($_REQUEST['BackURL']);
|
|
||||||
return $this->controller->redirect($url);
|
|
||||||
} else {
|
|
||||||
// Redirect to default location - the login form saying "You are logged in as..."
|
|
||||||
$redirectURL = HTTP::setGetVar(
|
|
||||||
'BackURL',
|
|
||||||
Director::absoluteBaseURL(),
|
|
||||||
$this->controller->Link('login')
|
|
||||||
);
|
|
||||||
return $this->controller->redirect($redirectURL);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->clearMessage();
|
|
||||||
$this->sessionMessage(
|
|
||||||
_t(
|
|
||||||
'Member.INVALIDNEWPASSWORD',
|
|
||||||
"We couldn't accept that password: {password}",
|
|
||||||
array('password' => nl2br("\n".Convert::raw2xml($isValid->starredList())))
|
|
||||||
),
|
|
||||||
"bad",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
|
|
||||||
return $this->controller->redirect($this->controller->Link('changepassword'));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->clearMessage();
|
$this->clearMessage();
|
||||||
$this->sessionMessage(
|
$this->sessionMessage(
|
||||||
_t('Member.ERRORNEWPASSWORD', "You have entered your new password differently, try again"),
|
_t('Member.ERRORNEWPASSWORD', "You have entered your new password differently, try again"),
|
||||||
"bad"
|
"bad"
|
||||||
);
|
);
|
||||||
|
|
||||||
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
|
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
|
||||||
return $this->controller->redirect($this->controller->Link('changepassword'));
|
return $this->controller->redirect($this->controller->Link('changepassword'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the new password is accepted
|
||||||
|
$validationResult = $member->changePassword($data['NewPassword1']);
|
||||||
|
if (!$validationResult->isValid()) {
|
||||||
|
$this->setSessionValidationResult($validationResult);
|
||||||
|
return $this->controller->redirect($this->controller->Link('changepassword'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear locked out status
|
||||||
|
$member->LockedOutUntil = null;
|
||||||
|
$member->FailedLoginCount = null;
|
||||||
|
$member->write();
|
||||||
|
|
||||||
|
if ($member->canLogIn()->isValid()) {
|
||||||
|
$member->logIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Add confirmation message to login redirect
|
||||||
|
Session::clear('AutoLoginHash');
|
||||||
|
|
||||||
|
if (!empty($_REQUEST['BackURL'])
|
||||||
|
// absolute redirection URLs may cause spoofing
|
||||||
|
&& Director::is_site_url($_REQUEST['BackURL'])
|
||||||
|
) {
|
||||||
|
$url = Director::absoluteURL($_REQUEST['BackURL']);
|
||||||
|
return $this->controller->redirect($url);
|
||||||
|
} else {
|
||||||
|
// Redirect to default location - the login form saying "You are logged in as..."
|
||||||
|
$redirectURL = HTTP::setGetVar(
|
||||||
|
'BackURL',
|
||||||
|
Director::absoluteBaseURL(),
|
||||||
|
$this->controller->Link('login')
|
||||||
|
);
|
||||||
|
return $this->controller->redirect($redirectURL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,436 +53,436 @@ use SilverStripe\View\Requirements;
|
|||||||
class Group extends DataObject
|
class Group extends DataObject
|
||||||
{
|
{
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
"Title" => "Varchar(255)",
|
"Title" => "Varchar(255)",
|
||||||
"Description" => "Text",
|
"Description" => "Text",
|
||||||
"Code" => "Varchar(255)",
|
"Code" => "Varchar(255)",
|
||||||
"Locked" => "Boolean",
|
"Locked" => "Boolean",
|
||||||
"Sort" => "Int",
|
"Sort" => "Int",
|
||||||
"HtmlEditorConfig" => "Text"
|
"HtmlEditorConfig" => "Text"
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $has_one = array(
|
private static $has_one = array(
|
||||||
"Parent" => "SilverStripe\\Security\\Group",
|
"Parent" => "SilverStripe\\Security\\Group",
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $has_many = array(
|
private static $has_many = array(
|
||||||
"Permissions" => "SilverStripe\\Security\\Permission",
|
"Permissions" => "SilverStripe\\Security\\Permission",
|
||||||
"Groups" => "SilverStripe\\Security\\Group"
|
"Groups" => "SilverStripe\\Security\\Group"
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $many_many = array(
|
private static $many_many = array(
|
||||||
"Members" => "SilverStripe\\Security\\Member",
|
"Members" => "SilverStripe\\Security\\Member",
|
||||||
"Roles" => "SilverStripe\\Security\\PermissionRole",
|
"Roles" => "SilverStripe\\Security\\PermissionRole",
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $extensions = array(
|
private static $extensions = array(
|
||||||
"SilverStripe\\ORM\\Hierarchy\\Hierarchy",
|
"SilverStripe\\ORM\\Hierarchy\\Hierarchy",
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $table_name = "Group";
|
private static $table_name = "Group";
|
||||||
|
|
||||||
public function populateDefaults()
|
public function populateDefaults()
|
||||||
{
|
{
|
||||||
parent::populateDefaults();
|
parent::populateDefaults();
|
||||||
|
|
||||||
if (!$this->Title) {
|
if (!$this->Title) {
|
||||||
$this->Title = _t('SecurityAdmin.NEWGROUP', "New Group");
|
$this->Title = _t('SecurityAdmin.NEWGROUP', "New Group");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllChildren()
|
public function getAllChildren()
|
||||||
{
|
{
|
||||||
$doSet = new ArrayList();
|
$doSet = new ArrayList();
|
||||||
|
|
||||||
$children = Group::get()->filter("ParentID", $this->ID);
|
$children = Group::get()->filter("ParentID", $this->ID);
|
||||||
foreach($children as $child) {
|
foreach ($children as $child) {
|
||||||
$doSet->push($child);
|
$doSet->push($child);
|
||||||
$doSet->merge($child->getAllChildren());
|
$doSet->merge($child->getAllChildren());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $doSet;
|
return $doSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caution: Only call on instances, not through a singleton.
|
* Caution: Only call on instances, not through a singleton.
|
||||||
* The "root group" fields will be created through {@link SecurityAdmin->EditForm()}.
|
* The "root group" fields will be created through {@link SecurityAdmin->EditForm()}.
|
||||||
*
|
*
|
||||||
* @return FieldList
|
* @return FieldList
|
||||||
*/
|
*/
|
||||||
public function getCMSFields()
|
public function getCMSFields()
|
||||||
{
|
{
|
||||||
$fields = new FieldList(
|
$fields = new FieldList(
|
||||||
new TabSet(
|
new TabSet(
|
||||||
"Root",
|
"Root",
|
||||||
new Tab(
|
new Tab(
|
||||||
'Members',
|
'Members',
|
||||||
_t('SecurityAdmin.MEMBERS', 'Members'),
|
_t('SecurityAdmin.MEMBERS', 'Members'),
|
||||||
new TextField("Title", $this->fieldLabel('Title')),
|
new TextField("Title", $this->fieldLabel('Title')),
|
||||||
$parentidfield = DropdownField::create(
|
$parentidfield = DropdownField::create(
|
||||||
'ParentID',
|
'ParentID',
|
||||||
$this->fieldLabel('Parent'),
|
$this->fieldLabel('Parent'),
|
||||||
Group::get()->exclude('ID', $this->ID)->map('ID', 'Breadcrumbs')
|
Group::get()->exclude('ID', $this->ID)->map('ID', 'Breadcrumbs')
|
||||||
)->setEmptyString(' '),
|
)->setEmptyString(' '),
|
||||||
new TextareaField('Description', $this->fieldLabel('Description'))
|
new TextareaField('Description', $this->fieldLabel('Description'))
|
||||||
),
|
),
|
||||||
$permissionsTab = new Tab(
|
$permissionsTab = new Tab(
|
||||||
'Permissions',
|
'Permissions',
|
||||||
_t('SecurityAdmin.PERMISSIONS', 'Permissions'),
|
_t('SecurityAdmin.PERMISSIONS', 'Permissions'),
|
||||||
$permissionsField = new PermissionCheckboxSetField(
|
$permissionsField = new PermissionCheckboxSetField(
|
||||||
'Permissions',
|
'Permissions',
|
||||||
false,
|
false,
|
||||||
'SilverStripe\\Security\\Permission',
|
'SilverStripe\\Security\\Permission',
|
||||||
'GroupID',
|
'GroupID',
|
||||||
$this
|
$this
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$parentidfield->setDescription(
|
$parentidfield->setDescription(
|
||||||
_t('Group.GroupReminder', 'If you choose a parent group, this group will take all it\'s roles')
|
_t('Group.GroupReminder', 'If you choose a parent group, this group will take all it\'s roles')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filter permissions
|
// Filter permissions
|
||||||
// TODO SecurityAdmin coupling, not easy to get to the form fields through GridFieldDetailForm
|
// TODO SecurityAdmin coupling, not easy to get to the form fields through GridFieldDetailForm
|
||||||
$permissionsField->setHiddenPermissions((array)Config::inst()->get('SilverStripe\\Admin\\SecurityAdmin', 'hidden_permissions'));
|
$permissionsField->setHiddenPermissions((array)Config::inst()->get('SilverStripe\\Admin\\SecurityAdmin', 'hidden_permissions'));
|
||||||
|
|
||||||
if($this->ID) {
|
if ($this->ID) {
|
||||||
$group = $this;
|
$group = $this;
|
||||||
$config = GridFieldConfig_RelationEditor::create();
|
$config = GridFieldConfig_RelationEditor::create();
|
||||||
$config->addComponent(new GridFieldButtonRow('after'));
|
$config->addComponent(new GridFieldButtonRow('after'));
|
||||||
$config->addComponents(new GridFieldExportButton('buttons-after-left'));
|
$config->addComponents(new GridFieldExportButton('buttons-after-left'));
|
||||||
$config->addComponents(new GridFieldPrintButton('buttons-after-left'));
|
$config->addComponents(new GridFieldPrintButton('buttons-after-left'));
|
||||||
/** @var GridFieldAddExistingAutocompleter $autocompleter */
|
/** @var GridFieldAddExistingAutocompleter $autocompleter */
|
||||||
$autocompleter = $config->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldAddExistingAutocompleter');
|
$autocompleter = $config->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldAddExistingAutocompleter');
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
$autocompleter
|
$autocompleter
|
||||||
->setResultsFormat('$Title ($Email)')
|
->setResultsFormat('$Title ($Email)')
|
||||||
->setSearchFields(array('FirstName', 'Surname', 'Email'));
|
->setSearchFields(array('FirstName', 'Surname', 'Email'));
|
||||||
/** @var GridFieldDetailForm $detailForm */
|
/** @var GridFieldDetailForm $detailForm */
|
||||||
$detailForm = $config->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDetailForm');
|
$detailForm = $config->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDetailForm');
|
||||||
$detailForm
|
$detailForm
|
||||||
->setValidator(Member_Validator::create())
|
->setValidator(Member_Validator::create())
|
||||||
->setItemEditFormCallback(function($form, $component) use($group) {
|
->setItemEditFormCallback(function ($form, $component) use ($group) {
|
||||||
/** @var Form $form */
|
/** @var Form $form */
|
||||||
$record = $form->getRecord();
|
$record = $form->getRecord();
|
||||||
$groupsField = $form->Fields()->dataFieldByName('DirectGroups');
|
$groupsField = $form->Fields()->dataFieldByName('DirectGroups');
|
||||||
if($groupsField) {
|
if ($groupsField) {
|
||||||
// If new records are created in a group context,
|
// If new records are created in a group context,
|
||||||
// set this group by default.
|
// set this group by default.
|
||||||
if($record && !$record->ID) {
|
if ($record && !$record->ID) {
|
||||||
$groupsField->setValue($group->ID);
|
$groupsField->setValue($group->ID);
|
||||||
} elseif($record && $record->ID) {
|
} elseif ($record && $record->ID) {
|
||||||
// TODO Mark disabled once chosen.js supports it
|
// TODO Mark disabled once chosen.js supports it
|
||||||
// $groupsField->setDisabledItems(array($group->ID));
|
// $groupsField->setDisabledItems(array($group->ID));
|
||||||
$form->Fields()->replaceField(
|
$form->Fields()->replaceField(
|
||||||
'DirectGroups',
|
'DirectGroups',
|
||||||
$groupsField->performReadonlyTransformation()
|
$groupsField->performReadonlyTransformation()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$memberList = GridField::create('Members',false, $this->DirectMembers(), $config)
|
$memberList = GridField::create('Members', false, $this->DirectMembers(), $config)
|
||||||
->addExtraClass('members_grid');
|
->addExtraClass('members_grid');
|
||||||
// @todo Implement permission checking on GridField
|
// @todo Implement permission checking on GridField
|
||||||
//$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
|
//$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
|
||||||
$fields->addFieldToTab('Root.Members', $memberList);
|
$fields->addFieldToTab('Root.Members', $memberList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add a dropdown for HTML editor configurations if more than one is available.
|
// Only add a dropdown for HTML editor configurations if more than one is available.
|
||||||
// Otherwise Member->getHtmlEditorConfigForCMS() will default to the 'cms' configuration.
|
// Otherwise Member->getHtmlEditorConfigForCMS() will default to the 'cms' configuration.
|
||||||
$editorConfigMap = HTMLEditorConfig::get_available_configs_map();
|
$editorConfigMap = HTMLEditorConfig::get_available_configs_map();
|
||||||
if(count($editorConfigMap) > 1) {
|
if (count($editorConfigMap) > 1) {
|
||||||
$fields->addFieldToTab(
|
$fields->addFieldToTab(
|
||||||
'Root.Permissions',
|
'Root.Permissions',
|
||||||
new DropdownField(
|
new DropdownField(
|
||||||
'HtmlEditorConfig',
|
'HtmlEditorConfig',
|
||||||
'HTML Editor Configuration',
|
'HTML Editor Configuration',
|
||||||
$editorConfigMap
|
$editorConfigMap
|
||||||
),
|
),
|
||||||
'Permissions'
|
'Permissions'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Permission::check('EDIT_PERMISSIONS')) {
|
if (!Permission::check('EDIT_PERMISSIONS')) {
|
||||||
$fields->removeFieldFromTab('Root', 'Permissions');
|
$fields->removeFieldFromTab('Root', 'Permissions');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show the "Roles" tab if permissions are granted to edit them,
|
// Only show the "Roles" tab if permissions are granted to edit them,
|
||||||
// and at least one role exists
|
// and at least one role exists
|
||||||
if(Permission::check('APPLY_ROLES') && DataObject::get('SilverStripe\\Security\\PermissionRole')) {
|
if (Permission::check('APPLY_ROLES') && DataObject::get('SilverStripe\\Security\\PermissionRole')) {
|
||||||
$fields->findOrMakeTab('Root.Roles', _t('SecurityAdmin.ROLES', 'Roles'));
|
$fields->findOrMakeTab('Root.Roles', _t('SecurityAdmin.ROLES', 'Roles'));
|
||||||
$fields->addFieldToTab(
|
$fields->addFieldToTab(
|
||||||
'Root.Roles',
|
'Root.Roles',
|
||||||
new LiteralField(
|
new LiteralField(
|
||||||
"",
|
"",
|
||||||
"<p>" .
|
"<p>" .
|
||||||
_t(
|
_t(
|
||||||
'SecurityAdmin.ROLESDESCRIPTION',
|
'SecurityAdmin.ROLESDESCRIPTION',
|
||||||
"Roles are predefined sets of permissions, and can be assigned to groups.<br />"
|
"Roles are predefined sets of permissions, and can be assigned to groups.<br />"
|
||||||
. "They are inherited from parent groups if required."
|
. "They are inherited from parent groups if required."
|
||||||
) . '<br />' .
|
) . '<br />' .
|
||||||
sprintf(
|
sprintf(
|
||||||
'<a href="%s" class="add-role">%s</a>',
|
'<a href="%s" class="add-role">%s</a>',
|
||||||
SecurityAdmin::singleton()->Link('show/root#Root_Roles'),
|
SecurityAdmin::singleton()->Link('show/root#Root_Roles'),
|
||||||
// TODO This should include #Root_Roles to switch directly to the tab,
|
// TODO This should include #Root_Roles to switch directly to the tab,
|
||||||
// but tabstrip.js doesn't display tabs when directly adressed through a URL pragma
|
// but tabstrip.js doesn't display tabs when directly adressed through a URL pragma
|
||||||
_t('Group.RolesAddEditLink', 'Manage roles')
|
_t('Group.RolesAddEditLink', 'Manage roles')
|
||||||
) .
|
) .
|
||||||
"</p>"
|
"</p>"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add roles (and disable all checkboxes for inherited roles)
|
// Add roles (and disable all checkboxes for inherited roles)
|
||||||
$allRoles = PermissionRole::get();
|
$allRoles = PermissionRole::get();
|
||||||
if(!Permission::check('ADMIN')) {
|
if (!Permission::check('ADMIN')) {
|
||||||
$allRoles = $allRoles->filter("OnlyAdminCanApply", 0);
|
$allRoles = $allRoles->filter("OnlyAdminCanApply", 0);
|
||||||
}
|
}
|
||||||
if($this->ID) {
|
if ($this->ID) {
|
||||||
$groupRoles = $this->Roles();
|
$groupRoles = $this->Roles();
|
||||||
$inheritedRoles = new ArrayList();
|
$inheritedRoles = new ArrayList();
|
||||||
$ancestors = $this->getAncestors();
|
$ancestors = $this->getAncestors();
|
||||||
foreach($ancestors as $ancestor) {
|
foreach ($ancestors as $ancestor) {
|
||||||
$ancestorRoles = $ancestor->Roles();
|
$ancestorRoles = $ancestor->Roles();
|
||||||
if ($ancestorRoles) {
|
if ($ancestorRoles) {
|
||||||
$inheritedRoles->merge($ancestorRoles);
|
$inheritedRoles->merge($ancestorRoles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$groupRoleIDs = $groupRoles->column('ID') + $inheritedRoles->column('ID');
|
$groupRoleIDs = $groupRoles->column('ID') + $inheritedRoles->column('ID');
|
||||||
$inheritedRoleIDs = $inheritedRoles->column('ID');
|
$inheritedRoleIDs = $inheritedRoles->column('ID');
|
||||||
} else {
|
} else {
|
||||||
$groupRoleIDs = array();
|
$groupRoleIDs = array();
|
||||||
$inheritedRoleIDs = array();
|
$inheritedRoleIDs = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$rolesField = ListboxField::create('Roles', false, $allRoles->map()->toArray())
|
$rolesField = ListboxField::create('Roles', false, $allRoles->map()->toArray())
|
||||||
->setDefaultItems($groupRoleIDs)
|
->setDefaultItems($groupRoleIDs)
|
||||||
->setAttribute('data-placeholder', _t('Group.AddRole', 'Add a role for this group'))
|
->setAttribute('data-placeholder', _t('Group.AddRole', 'Add a role for this group'))
|
||||||
->setDisabledItems($inheritedRoleIDs);
|
->setDisabledItems($inheritedRoleIDs);
|
||||||
if(!$allRoles->count()) {
|
if (!$allRoles->count()) {
|
||||||
$rolesField->setAttribute('data-placeholder', _t('Group.NoRoles', 'No roles found'));
|
$rolesField->setAttribute('data-placeholder', _t('Group.NoRoles', 'No roles found'));
|
||||||
}
|
}
|
||||||
$fields->addFieldToTab('Root.Roles', $rolesField);
|
$fields->addFieldToTab('Root.Roles', $rolesField);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fields->push($idField = new HiddenField("ID"));
|
$fields->push($idField = new HiddenField("ID"));
|
||||||
|
|
||||||
$this->extend('updateCMSFields', $fields);
|
$this->extend('updateCMSFields', $fields);
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $includerelations Indicate if the labels returned include relation fields
|
* @param bool $includerelations Indicate if the labels returned include relation fields
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function fieldLabels($includerelations = true)
|
public function fieldLabels($includerelations = true)
|
||||||
{
|
{
|
||||||
$labels = parent::fieldLabels($includerelations);
|
$labels = parent::fieldLabels($includerelations);
|
||||||
$labels['Title'] = _t('SecurityAdmin.GROUPNAME', 'Group name');
|
$labels['Title'] = _t('SecurityAdmin.GROUPNAME', 'Group name');
|
||||||
$labels['Description'] = _t('Group.Description', 'Description');
|
$labels['Description'] = _t('Group.Description', 'Description');
|
||||||
$labels['Code'] = _t('Group.Code', 'Group Code', 'Programmatical code identifying a group');
|
$labels['Code'] = _t('Group.Code', 'Group Code', 'Programmatical code identifying a group');
|
||||||
$labels['Locked'] = _t('Group.Locked', 'Locked?', 'Group is locked in the security administration area');
|
$labels['Locked'] = _t('Group.Locked', 'Locked?', 'Group is locked in the security administration area');
|
||||||
$labels['Sort'] = _t('Group.Sort', 'Sort Order');
|
$labels['Sort'] = _t('Group.Sort', 'Sort Order');
|
||||||
if($includerelations){
|
if ($includerelations) {
|
||||||
$labels['Parent'] = _t('Group.Parent', 'Parent Group', 'One group has one parent group');
|
$labels['Parent'] = _t('Group.Parent', 'Parent Group', 'One group has one parent group');
|
||||||
$labels['Permissions'] = _t('Group.has_many_Permissions', 'Permissions', 'One group has many permissions');
|
$labels['Permissions'] = _t('Group.has_many_Permissions', 'Permissions', 'One group has many permissions');
|
||||||
$labels['Members'] = _t('Group.many_many_Members', 'Members', 'One group has many members');
|
$labels['Members'] = _t('Group.many_many_Members', 'Members', 'One group has many members');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $labels;
|
return $labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get many-many relation to {@link Member},
|
* Get many-many relation to {@link Member},
|
||||||
* including all members which are "inherited" from children groups of this record.
|
* including all members which are "inherited" from children groups of this record.
|
||||||
* See {@link DirectMembers()} for retrieving members without any inheritance.
|
* See {@link DirectMembers()} for retrieving members without any inheritance.
|
||||||
*
|
*
|
||||||
* @param String $filter
|
* @param String $filter
|
||||||
* @return ManyManyList
|
* @return ManyManyList
|
||||||
*/
|
*/
|
||||||
public function Members($filter = '')
|
public function Members($filter = '')
|
||||||
{
|
{
|
||||||
// First get direct members as a base result
|
// First get direct members as a base result
|
||||||
$result = $this->DirectMembers();
|
$result = $this->DirectMembers();
|
||||||
|
|
||||||
// Unsaved group cannot have child groups because its ID is still 0.
|
// Unsaved group cannot have child groups because its ID is still 0.
|
||||||
if (!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the default foreign key filter in prep for re-applying a filter containing all children groups.
|
// Remove the default foreign key filter in prep for re-applying a filter containing all children groups.
|
||||||
// Filters are conjunctive in DataQuery by default, so this filter would otherwise overrule any less specific
|
// Filters are conjunctive in DataQuery by default, so this filter would otherwise overrule any less specific
|
||||||
// ones.
|
// ones.
|
||||||
if(!($result instanceof UnsavedRelationList)) {
|
if (!($result instanceof UnsavedRelationList)) {
|
||||||
$result = $result->alterDataQuery(function($query){
|
$result = $result->alterDataQuery(function ($query) {
|
||||||
/** @var DataQuery $query */
|
/** @var DataQuery $query */
|
||||||
$query->removeFilterOn('Group_Members');
|
$query->removeFilterOn('Group_Members');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Now set all children groups as a new foreign key
|
// Now set all children groups as a new foreign key
|
||||||
$groups = Group::get()->byIDs($this->collateFamilyIDs());
|
$groups = Group::get()->byIDs($this->collateFamilyIDs());
|
||||||
$result = $result->forForeignID($groups->column('ID'))->where($filter);
|
$result = $result->forForeignID($groups->column('ID'))->where($filter);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return only the members directly added to this group
|
* Return only the members directly added to this group
|
||||||
*/
|
*/
|
||||||
public function DirectMembers()
|
public function DirectMembers()
|
||||||
{
|
{
|
||||||
return $this->getManyManyComponents('Members');
|
return $this->getManyManyComponents('Members');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a set of this record's "family" of IDs - the IDs of
|
* Return a set of this record's "family" of IDs - the IDs of
|
||||||
* this record and all its descendants.
|
* this record and all its descendants.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function collateFamilyIDs()
|
public function collateFamilyIDs()
|
||||||
{
|
{
|
||||||
if (!$this->exists()) {
|
if (!$this->exists()) {
|
||||||
throw new \InvalidArgumentException("Cannot call collateFamilyIDs on unsaved Group.");
|
throw new \InvalidArgumentException("Cannot call collateFamilyIDs on unsaved Group.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$familyIDs = array();
|
$familyIDs = array();
|
||||||
$chunkToAdd = array($this->ID);
|
$chunkToAdd = array($this->ID);
|
||||||
|
|
||||||
while($chunkToAdd) {
|
while ($chunkToAdd) {
|
||||||
$familyIDs = array_merge($familyIDs,$chunkToAdd);
|
$familyIDs = array_merge($familyIDs, $chunkToAdd);
|
||||||
|
|
||||||
// Get the children of *all* the groups identified in the previous chunk.
|
// Get the children of *all* the groups identified in the previous chunk.
|
||||||
// This minimises the number of SQL queries necessary
|
// This minimises the number of SQL queries necessary
|
||||||
$chunkToAdd = Group::get()->filter("ParentID", $chunkToAdd)->column('ID');
|
$chunkToAdd = Group::get()->filter("ParentID", $chunkToAdd)->column('ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $familyIDs;
|
return $familyIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of the IDs of this group and all its parents
|
* Returns an array of the IDs of this group and all its parents
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function collateAncestorIDs()
|
public function collateAncestorIDs()
|
||||||
{
|
{
|
||||||
$parent = $this;
|
$parent = $this;
|
||||||
$items = [];
|
$items = [];
|
||||||
while(isset($parent) && $parent instanceof Group) {
|
while (isset($parent) && $parent instanceof Group) {
|
||||||
$items[] = $parent->ID;
|
$items[] = $parent->ID;
|
||||||
$parent = $parent->Parent;
|
$parent = $parent->Parent;
|
||||||
}
|
}
|
||||||
return $items;
|
return $items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This isn't a decendant of SiteTree, but needs this in case
|
* This isn't a decendant of SiteTree, but needs this in case
|
||||||
* the group is "reorganised";
|
* the group is "reorganised";
|
||||||
*/
|
*/
|
||||||
public function cmsCleanup_parentChanged()
|
public function cmsCleanup_parentChanged()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override this so groups are ordered in the CMS
|
* Override this so groups are ordered in the CMS
|
||||||
*/
|
*/
|
||||||
public function stageChildren()
|
public function stageChildren()
|
||||||
{
|
{
|
||||||
return Group::get()
|
return Group::get()
|
||||||
->filter("ParentID", $this->ID)
|
->filter("ParentID", $this->ID)
|
||||||
->exclude("ID", $this->ID)
|
->exclude("ID", $this->ID)
|
||||||
->sort('"Sort"');
|
->sort('"Sort"');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTreeTitle()
|
public function getTreeTitle()
|
||||||
{
|
{
|
||||||
if($this->hasMethod('alternateTreeTitle')) {
|
if ($this->hasMethod('alternateTreeTitle')) {
|
||||||
return $this->alternateTreeTitle();
|
return $this->alternateTreeTitle();
|
||||||
}
|
}
|
||||||
return htmlspecialchars($this->Title, ENT_QUOTES);
|
return htmlspecialchars($this->Title, ENT_QUOTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overloaded to ensure the code is always descent.
|
* Overloaded to ensure the code is always descent.
|
||||||
*
|
*
|
||||||
* @param string
|
* @param string
|
||||||
*/
|
*/
|
||||||
public function setCode($val)
|
public function setCode($val)
|
||||||
{
|
{
|
||||||
$this->setField("Code", Convert::raw2url($val));
|
$this->setField("Code", Convert::raw2url($val));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validate()
|
public function validate()
|
||||||
{
|
{
|
||||||
$result = parent::validate();
|
$result = parent::validate();
|
||||||
|
|
||||||
// Check if the new group hierarchy would add certain "privileged permissions",
|
// Check if the new group hierarchy would add certain "privileged permissions",
|
||||||
// and require an admin to perform this change in case it does.
|
// and require an admin to perform this change in case it does.
|
||||||
// This prevents "sub-admin" users with group editing permissions to increase their privileges.
|
// This prevents "sub-admin" users with group editing permissions to increase their privileges.
|
||||||
if($this->Parent()->exists() && !Permission::check('ADMIN')) {
|
if ($this->Parent()->exists() && !Permission::check('ADMIN')) {
|
||||||
$inheritedCodes = Permission::get()
|
$inheritedCodes = Permission::get()
|
||||||
->filter('GroupID', $this->Parent()->collateAncestorIDs())
|
->filter('GroupID', $this->Parent()->collateAncestorIDs())
|
||||||
->column('Code');
|
->column('Code');
|
||||||
$privilegedCodes = Config::inst()->get('SilverStripe\\Security\\Permission', 'privileged_permissions');
|
$privilegedCodes = Config::inst()->get('SilverStripe\\Security\\Permission', 'privileged_permissions');
|
||||||
if(array_intersect($inheritedCodes, $privilegedCodes)) {
|
if (array_intersect($inheritedCodes, $privilegedCodes)) {
|
||||||
$result->addError(sprintf(
|
$result->addError(sprintf(
|
||||||
_t(
|
_t(
|
||||||
'Group.HierarchyPermsError',
|
'Group.HierarchyPermsError',
|
||||||
'Can\'t assign parent group "%s" with privileged permissions (requires ADMIN access)'
|
'Can\'t assign parent group "%s" with privileged permissions (requires ADMIN access)'
|
||||||
),
|
),
|
||||||
$this->Parent()->Title
|
$this->Parent()->Title
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onBeforeWrite()
|
public function onBeforeWrite()
|
||||||
{
|
{
|
||||||
parent::onBeforeWrite();
|
parent::onBeforeWrite();
|
||||||
|
|
||||||
// Only set code property when the group has a custom title, and no code exists.
|
// Only set code property when the group has a custom title, and no code exists.
|
||||||
// The "Code" attribute is usually treated as a more permanent identifier than database IDs
|
// The "Code" attribute is usually treated as a more permanent identifier than database IDs
|
||||||
// in custom application logic, so can't be changed after its first set.
|
// in custom application logic, so can't be changed after its first set.
|
||||||
if(!$this->Code && $this->Title != _t('SecurityAdmin.NEWGROUP',"New Group")) {
|
if (!$this->Code && $this->Title != _t('SecurityAdmin.NEWGROUP', "New Group")) {
|
||||||
$this->setCode($this->Title);
|
$this->setCode($this->Title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onBeforeDelete()
|
public function onBeforeDelete()
|
||||||
{
|
{
|
||||||
parent::onBeforeDelete();
|
parent::onBeforeDelete();
|
||||||
|
|
||||||
// if deleting this group, delete it's children as well
|
// if deleting this group, delete it's children as well
|
||||||
foreach($this->Groups() as $group) {
|
foreach ($this->Groups() as $group) {
|
||||||
$group->delete();
|
$group->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete associated permissions
|
// Delete associated permissions
|
||||||
foreach($this->Permissions() as $permission) {
|
foreach ($this->Permissions() as $permission) {
|
||||||
$permission->delete();
|
$permission->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for permission-code CMS_ACCESS_SecurityAdmin.
|
* Checks for permission-code CMS_ACCESS_SecurityAdmin.
|
||||||
* If the group has ADMIN permissions, it requires the user to have ADMIN permissions as well.
|
* If the group has ADMIN permissions, it requires the user to have ADMIN permissions as well.
|
||||||
*
|
*
|
||||||
* @param $member Member
|
* @param $member Member
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function canEdit($member = null)
|
public function canEdit($member = null)
|
||||||
{
|
{
|
||||||
if (!$member || !(is_a($member, 'SilverStripe\\Security\\Member')) || is_numeric($member)) {
|
if (!$member || !(is_a($member, 'SilverStripe\\Security\\Member')) || is_numeric($member)) {
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
// extended access checks
|
// extended access checks
|
||||||
$results = $this->extend('canEdit', $member);
|
$results = $this->extend('canEdit', $member);
|
||||||
if ($results && is_array($results)) {
|
if ($results && is_array($results)) {
|
||||||
if (!min($results)) {
|
if (!min($results)) {
|
||||||
return false;
|
return false;
|
||||||
@ -490,48 +490,48 @@ class Group extends DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (// either we have an ADMIN
|
if (// either we have an ADMIN
|
||||||
(bool)Permission::checkMember($member, "ADMIN")
|
(bool)Permission::checkMember($member, "ADMIN")
|
||||||
|| (
|
|| (
|
||||||
// or a privileged CMS user and a group without ADMIN permissions.
|
// or a privileged CMS user and a group without ADMIN permissions.
|
||||||
// without this check, a user would be able to add himself to an administrators group
|
// without this check, a user would be able to add himself to an administrators group
|
||||||
// with just access to the "Security" admin interface
|
// with just access to the "Security" admin interface
|
||||||
Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin") &&
|
Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin") &&
|
||||||
!Permission::get()->filter(array('GroupID' => $this->ID, 'Code' => 'ADMIN'))->exists()
|
!Permission::get()->filter(array('GroupID' => $this->ID, 'Code' => 'ADMIN'))->exists()
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for permission-code CMS_ACCESS_SecurityAdmin.
|
* Checks for permission-code CMS_ACCESS_SecurityAdmin.
|
||||||
*
|
*
|
||||||
* @param $member Member
|
* @param $member Member
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function canView($member = null)
|
public function canView($member = null)
|
||||||
{
|
{
|
||||||
if (!$member || !(is_a($member, 'SilverStripe\\Security\\Member')) || is_numeric($member)) {
|
if (!$member || !(is_a($member, 'SilverStripe\\Security\\Member')) || is_numeric($member)) {
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
// extended access checks
|
// extended access checks
|
||||||
$results = $this->extend('canView', $member);
|
$results = $this->extend('canView', $member);
|
||||||
if ($results && is_array($results)) {
|
if ($results && is_array($results)) {
|
||||||
if (!min($results)) {
|
if (!min($results)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// user needs access to CMS_ACCESS_SecurityAdmin
|
// user needs access to CMS_ACCESS_SecurityAdmin
|
||||||
if (Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin")) {
|
if (Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canDelete($member = null)
|
public function canDelete($member = null)
|
||||||
{
|
{
|
||||||
@ -539,30 +539,30 @@ class Group extends DataObject
|
|||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
// extended access checks
|
// extended access checks
|
||||||
$results = $this->extend('canDelete', $member);
|
$results = $this->extend('canDelete', $member);
|
||||||
if ($results && is_array($results)) {
|
if ($results && is_array($results)) {
|
||||||
if (!min($results)) {
|
if (!min($results)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->canEdit($member);
|
return $this->canEdit($member);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all of the children for the CMS Tree.
|
* Returns all of the children for the CMS Tree.
|
||||||
* Filters to only those groups that the current user can edit
|
* Filters to only those groups that the current user can edit
|
||||||
*/
|
*/
|
||||||
public function AllChildrenIncludingDeleted()
|
public function AllChildrenIncludingDeleted()
|
||||||
{
|
{
|
||||||
/** @var Hierarchy $extInstance */
|
/** @var Hierarchy $extInstance */
|
||||||
$extInstance = $this->getExtensionInstance('SilverStripe\\ORM\\Hierarchy\\Hierarchy');
|
$extInstance = $this->getExtensionInstance('SilverStripe\\ORM\\Hierarchy\\Hierarchy');
|
||||||
$extInstance->setOwner($this);
|
$extInstance->setOwner($this);
|
||||||
$children = $extInstance->AllChildrenIncludingDeleted();
|
$children = $extInstance->AllChildrenIncludingDeleted();
|
||||||
$extInstance->clearOwner();
|
$extInstance->clearOwner();
|
||||||
|
|
||||||
$filteredChildren = new ArrayList();
|
$filteredChildren = new ArrayList();
|
||||||
|
|
||||||
if ($children) {
|
if ($children) {
|
||||||
foreach ($children as $child) {
|
foreach ($children as $child) {
|
||||||
@ -570,46 +570,46 @@ class Group extends DataObject
|
|||||||
$filteredChildren->push($child);
|
$filteredChildren->push($child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $filteredChildren;
|
return $filteredChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add default records to database.
|
* Add default records to database.
|
||||||
*
|
*
|
||||||
* This function is called whenever the database is built, after the
|
* This function is called whenever the database is built, after the
|
||||||
* database tables have all been created.
|
* database tables have all been created.
|
||||||
*/
|
*/
|
||||||
public function requireDefaultRecords()
|
public function requireDefaultRecords()
|
||||||
{
|
{
|
||||||
parent::requireDefaultRecords();
|
parent::requireDefaultRecords();
|
||||||
|
|
||||||
// Add default author group if no other group exists
|
// Add default author group if no other group exists
|
||||||
$allGroups = DataObject::get('SilverStripe\\Security\\Group');
|
$allGroups = DataObject::get('SilverStripe\\Security\\Group');
|
||||||
if(!$allGroups->count()) {
|
if (!$allGroups->count()) {
|
||||||
$authorGroup = new Group();
|
$authorGroup = new Group();
|
||||||
$authorGroup->Code = 'content-authors';
|
$authorGroup->Code = 'content-authors';
|
||||||
$authorGroup->Title = _t('Group.DefaultGroupTitleContentAuthors', 'Content Authors');
|
$authorGroup->Title = _t('Group.DefaultGroupTitleContentAuthors', 'Content Authors');
|
||||||
$authorGroup->Sort = 1;
|
$authorGroup->Sort = 1;
|
||||||
$authorGroup->write();
|
$authorGroup->write();
|
||||||
Permission::grant($authorGroup->ID, 'CMS_ACCESS_CMSMain');
|
Permission::grant($authorGroup->ID, 'CMS_ACCESS_CMSMain');
|
||||||
Permission::grant($authorGroup->ID, 'CMS_ACCESS_AssetAdmin');
|
Permission::grant($authorGroup->ID, 'CMS_ACCESS_AssetAdmin');
|
||||||
Permission::grant($authorGroup->ID, 'CMS_ACCESS_ReportAdmin');
|
Permission::grant($authorGroup->ID, 'CMS_ACCESS_ReportAdmin');
|
||||||
Permission::grant($authorGroup->ID, 'SITETREE_REORGANISE');
|
Permission::grant($authorGroup->ID, 'SITETREE_REORGANISE');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add default admin group if none with permission code ADMIN exists
|
// Add default admin group if none with permission code ADMIN exists
|
||||||
$adminGroups = Permission::get_groups_by_permission('ADMIN');
|
$adminGroups = Permission::get_groups_by_permission('ADMIN');
|
||||||
if(!$adminGroups->count()) {
|
if (!$adminGroups->count()) {
|
||||||
$adminGroup = new Group();
|
$adminGroup = new Group();
|
||||||
$adminGroup->Code = 'administrators';
|
$adminGroup->Code = 'administrators';
|
||||||
$adminGroup->Title = _t('Group.DefaultGroupTitleAdministrators', 'Administrators');
|
$adminGroup->Title = _t('Group.DefaultGroupTitleAdministrators', 'Administrators');
|
||||||
$adminGroup->Sort = 0;
|
$adminGroup->Sort = 0;
|
||||||
$adminGroup->write();
|
$adminGroup->write();
|
||||||
Permission::grant($adminGroup->ID, 'ADMIN');
|
Permission::grant($adminGroup->ID, 'ADMIN');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Members are populated through Member->requireDefaultRecords()
|
// Members are populated through Member->requireDefaultRecords()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,380 +63,380 @@ use Zend_Locale_Format;
|
|||||||
class Member extends DataObject implements TemplateGlobalProvider
|
class Member extends DataObject implements TemplateGlobalProvider
|
||||||
{
|
{
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'FirstName' => 'Varchar',
|
'FirstName' => 'Varchar',
|
||||||
'Surname' => 'Varchar',
|
'Surname' => 'Varchar',
|
||||||
'Email' => 'Varchar(254)', // See RFC 5321, Section 4.5.3.1.3. (256 minus the < and > character)
|
'Email' => 'Varchar(254)', // See RFC 5321, Section 4.5.3.1.3. (256 minus the < and > character)
|
||||||
'TempIDHash' => 'Varchar(160)', // Temporary id used for cms re-authentication
|
'TempIDHash' => 'Varchar(160)', // Temporary id used for cms re-authentication
|
||||||
'TempIDExpired' => 'Datetime', // Expiry of temp login
|
'TempIDExpired' => 'Datetime', // Expiry of temp login
|
||||||
'Password' => 'Varchar(160)',
|
'Password' => 'Varchar(160)',
|
||||||
'AutoLoginHash' => 'Varchar(160)', // Used to auto-login the user on password reset
|
'AutoLoginHash' => 'Varchar(160)', // Used to auto-login the user on password reset
|
||||||
'AutoLoginExpired' => 'Datetime',
|
'AutoLoginExpired' => 'Datetime',
|
||||||
// This is an arbitrary code pointing to a PasswordEncryptor instance,
|
// This is an arbitrary code pointing to a PasswordEncryptor instance,
|
||||||
// not an actual encryption algorithm.
|
// not an actual encryption algorithm.
|
||||||
// Warning: Never change this field after its the first password hashing without
|
// Warning: Never change this field after its the first password hashing without
|
||||||
// providing a new cleartext password as well.
|
// providing a new cleartext password as well.
|
||||||
'PasswordEncryption' => "Varchar(50)",
|
'PasswordEncryption' => "Varchar(50)",
|
||||||
'Salt' => 'Varchar(50)',
|
'Salt' => 'Varchar(50)',
|
||||||
'PasswordExpiry' => 'Date',
|
'PasswordExpiry' => 'Date',
|
||||||
'LockedOutUntil' => 'Datetime',
|
'LockedOutUntil' => 'Datetime',
|
||||||
'Locale' => 'Varchar(6)',
|
'Locale' => 'Varchar(6)',
|
||||||
// handled in registerFailedLogin(), only used if $lock_out_after_incorrect_logins is set
|
// handled in registerFailedLogin(), only used if $lock_out_after_incorrect_logins is set
|
||||||
'FailedLoginCount' => 'Int',
|
'FailedLoginCount' => 'Int',
|
||||||
// In ISO format
|
// In ISO format
|
||||||
'DateFormat' => 'Varchar(30)',
|
'DateFormat' => 'Varchar(30)',
|
||||||
'TimeFormat' => 'Varchar(30)',
|
'TimeFormat' => 'Varchar(30)',
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $belongs_many_many = array(
|
private static $belongs_many_many = array(
|
||||||
'Groups' => 'SilverStripe\\Security\\Group',
|
'Groups' => 'SilverStripe\\Security\\Group',
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $has_many = array(
|
private static $has_many = array(
|
||||||
'LoggedPasswords' => 'SilverStripe\\Security\\MemberPassword',
|
'LoggedPasswords' => 'SilverStripe\\Security\\MemberPassword',
|
||||||
'RememberLoginHashes' => 'SilverStripe\\Security\\RememberLoginHash'
|
'RememberLoginHashes' => 'SilverStripe\\Security\\RememberLoginHash'
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $table_name = "Member";
|
private static $table_name = "Member";
|
||||||
|
|
||||||
private static $default_sort = '"Surname", "FirstName"';
|
private static $default_sort = '"Surname", "FirstName"';
|
||||||
|
|
||||||
private static $indexes = array(
|
private static $indexes = array(
|
||||||
'Email' => true,
|
'Email' => true,
|
||||||
//Removed due to duplicate null values causing MSSQL problems
|
//Removed due to duplicate null values causing MSSQL problems
|
||||||
//'AutoLoginHash' => Array('type'=>'unique', 'value'=>'AutoLoginHash', 'ignoreNulls'=>true)
|
//'AutoLoginHash' => Array('type'=>'unique', 'value'=>'AutoLoginHash', 'ignoreNulls'=>true)
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var boolean
|
* @var boolean
|
||||||
*/
|
*/
|
||||||
private static $notify_password_change = false;
|
private static $notify_password_change = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All searchable database columns
|
* All searchable database columns
|
||||||
* in this object, currently queried
|
* in this object, currently queried
|
||||||
* with a "column LIKE '%keywords%'
|
* with a "column LIKE '%keywords%'
|
||||||
* statement.
|
* statement.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
* @todo Generic implementation of $searchable_fields on DataObject,
|
* @todo Generic implementation of $searchable_fields on DataObject,
|
||||||
* with definition for different searching algorithms
|
* with definition for different searching algorithms
|
||||||
* (LIKE, FULLTEXT) and default FormFields to construct a searchform.
|
* (LIKE, FULLTEXT) and default FormFields to construct a searchform.
|
||||||
*/
|
*/
|
||||||
private static $searchable_fields = array(
|
private static $searchable_fields = array(
|
||||||
'FirstName',
|
'FirstName',
|
||||||
'Surname',
|
'Surname',
|
||||||
'Email',
|
'Email',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $summary_fields = array(
|
private static $summary_fields = array(
|
||||||
'FirstName',
|
'FirstName',
|
||||||
'Surname',
|
'Surname',
|
||||||
'Email',
|
'Email',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $casting = array(
|
private static $casting = array(
|
||||||
'Name' => 'Varchar',
|
'Name' => 'Varchar',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal-use only fields
|
* Internal-use only fields
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $hidden_fields = array(
|
private static $hidden_fields = array(
|
||||||
'AutoLoginHash',
|
'AutoLoginHash',
|
||||||
'AutoLoginExpired',
|
'AutoLoginExpired',
|
||||||
'PasswordEncryption',
|
'PasswordEncryption',
|
||||||
'PasswordExpiry',
|
'PasswordExpiry',
|
||||||
'LockedOutUntil',
|
'LockedOutUntil',
|
||||||
'TempIDHash',
|
'TempIDHash',
|
||||||
'TempIDExpired',
|
'TempIDExpired',
|
||||||
'Salt',
|
'Salt',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var array See {@link set_title_columns()}
|
* @var array See {@link set_title_columns()}
|
||||||
*/
|
*/
|
||||||
private static $title_format = null;
|
private static $title_format = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unique field used to identify this member.
|
* The unique field used to identify this member.
|
||||||
* By default, it's "Email", but another common
|
* By default, it's "Email", but another common
|
||||||
* field could be Username.
|
* field could be Username.
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var string
|
* @var string
|
||||||
* @skipUpgrade
|
* @skipUpgrade
|
||||||
*/
|
*/
|
||||||
private static $unique_identifier_field = 'Email';
|
private static $unique_identifier_field = 'Email';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object for validating user's password
|
* Object for validating user's password
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var PasswordValidator
|
* @var PasswordValidator
|
||||||
*/
|
*/
|
||||||
private static $password_validator = null;
|
private static $password_validator = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* The number of days that a password should be valid for.
|
* The number of days that a password should be valid for.
|
||||||
* By default, this is null, which means that passwords never expire
|
* By default, this is null, which means that passwords never expire
|
||||||
*/
|
*/
|
||||||
private static $password_expiry_days = null;
|
private static $password_expiry_days = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var Int Number of incorrect logins after which
|
* @var Int Number of incorrect logins after which
|
||||||
* the user is blocked from further attempts for the timespan
|
* the user is blocked from further attempts for the timespan
|
||||||
* defined in {@link $lock_out_delay_mins}.
|
* defined in {@link $lock_out_delay_mins}.
|
||||||
*/
|
*/
|
||||||
private static $lock_out_after_incorrect_logins = 10;
|
private static $lock_out_after_incorrect_logins = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var integer Minutes of enforced lockout after incorrect password attempts.
|
* @var integer Minutes of enforced lockout after incorrect password attempts.
|
||||||
* Only applies if {@link $lock_out_after_incorrect_logins} greater than 0.
|
* Only applies if {@link $lock_out_after_incorrect_logins} greater than 0.
|
||||||
*/
|
*/
|
||||||
private static $lock_out_delay_mins = 15;
|
private static $lock_out_delay_mins = 15;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var String If this is set, then a session cookie with the given name will be set on log-in,
|
* @var String If this is set, then a session cookie with the given name will be set on log-in,
|
||||||
* and cleared on logout.
|
* and cleared on logout.
|
||||||
*/
|
*/
|
||||||
private static $login_marker_cookie = null;
|
private static $login_marker_cookie = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that when a {@link Member} logs in, Member:session_regenerate_id()
|
* Indicates that when a {@link Member} logs in, Member:session_regenerate_id()
|
||||||
* should be called as a security precaution.
|
* should be called as a security precaution.
|
||||||
*
|
*
|
||||||
* This doesn't always work, especially if you're trying to set session cookies
|
* This doesn't always work, especially if you're trying to set session cookies
|
||||||
* across an entire site using the domain parameter to session_set_cookie_params()
|
* across an entire site using the domain parameter to session_set_cookie_params()
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var boolean
|
* @var boolean
|
||||||
*/
|
*/
|
||||||
private static $session_regenerate_id = true;
|
private static $session_regenerate_id = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default lifetime of temporary ids.
|
* Default lifetime of temporary ids.
|
||||||
*
|
*
|
||||||
* This is the period within which a user can be re-authenticated within the CMS by entering only their password
|
* This is the period within which a user can be re-authenticated within the CMS by entering only their password
|
||||||
* and without losing their workspace.
|
* and without losing their workspace.
|
||||||
*
|
*
|
||||||
* Any session expiration outside of this time will require them to login from the frontend using their full
|
* Any session expiration outside of this time will require them to login from the frontend using their full
|
||||||
* username and password.
|
* username and password.
|
||||||
*
|
*
|
||||||
* Defaults to 72 hours. Set to zero to disable expiration.
|
* Defaults to 72 hours. Set to zero to disable expiration.
|
||||||
*
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var int Lifetime in seconds
|
* @var int Lifetime in seconds
|
||||||
*/
|
*/
|
||||||
private static $temp_id_lifetime = 259200;
|
private static $temp_id_lifetime = 259200;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the locale is set to something sensible by default.
|
* Ensure the locale is set to something sensible by default.
|
||||||
*/
|
*/
|
||||||
public function populateDefaults()
|
public function populateDefaults()
|
||||||
{
|
{
|
||||||
parent::populateDefaults();
|
parent::populateDefaults();
|
||||||
$this->Locale = i18n::get_closest_translation(i18n::get_locale());
|
$this->Locale = i18n::get_closest_translation(i18n::get_locale());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function requireDefaultRecords()
|
public function requireDefaultRecords()
|
||||||
{
|
{
|
||||||
parent::requireDefaultRecords();
|
parent::requireDefaultRecords();
|
||||||
// Default groups should've been built by Group->requireDefaultRecords() already
|
// Default groups should've been built by Group->requireDefaultRecords() already
|
||||||
static::default_admin();
|
static::default_admin();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default admin record if it exists, or creates it otherwise if enabled
|
* Get the default admin record if it exists, or creates it otherwise if enabled
|
||||||
*
|
*
|
||||||
* @return Member
|
* @return Member
|
||||||
*/
|
*/
|
||||||
public static function default_admin()
|
public static function default_admin()
|
||||||
{
|
{
|
||||||
// Check if set
|
// Check if set
|
||||||
if (!Security::has_default_admin()) {
|
if (!Security::has_default_admin()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find or create ADMIN group
|
||||||
|
Group::singleton()->requireDefaultRecords();
|
||||||
|
$adminGroup = Permission::get_groups_by_permission('ADMIN')->first();
|
||||||
|
|
||||||
// Find or create ADMIN group
|
// Find member
|
||||||
Group::singleton()->requireDefaultRecords();
|
/** @skipUpgrade */
|
||||||
$adminGroup = Permission::get_groups_by_permission('ADMIN')->first();
|
$admin = Member::get()
|
||||||
|
->filter('Email', Security::default_admin_username())
|
||||||
|
->first();
|
||||||
|
if (!$admin) {
|
||||||
|
// 'Password' is not set to avoid creating
|
||||||
|
// persistent logins in the database. See Security::setDefaultAdmin().
|
||||||
|
// Set 'Email' to identify this as the default admin
|
||||||
|
$admin = Member::create();
|
||||||
|
$admin->FirstName = _t('Member.DefaultAdminFirstname', 'Default Admin');
|
||||||
|
$admin->Email = Security::default_admin_username();
|
||||||
|
$admin->write();
|
||||||
|
}
|
||||||
|
|
||||||
// Find member
|
// Ensure this user is in the admin group
|
||||||
/** @skipUpgrade */
|
if (!$admin->inGroup($adminGroup)) {
|
||||||
$admin = Member::get()
|
// Add member to group instead of adding group to member
|
||||||
->filter('Email', Security::default_admin_username())
|
// This bypasses the privilege escallation code in Member_GroupSet
|
||||||
->first();
|
$adminGroup
|
||||||
if(!$admin) {
|
->DirectMembers()
|
||||||
// 'Password' is not set to avoid creating
|
->add($admin);
|
||||||
// persistent logins in the database. See Security::setDefaultAdmin().
|
}
|
||||||
// Set 'Email' to identify this as the default admin
|
|
||||||
$admin = Member::create();
|
|
||||||
$admin->FirstName = _t('Member.DefaultAdminFirstname', 'Default Admin');
|
|
||||||
$admin->Email = Security::default_admin_username();
|
|
||||||
$admin->write();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure this user is in the admin group
|
return $admin;
|
||||||
if(!$admin->inGroup($adminGroup)) {
|
}
|
||||||
// Add member to group instead of adding group to member
|
|
||||||
// This bypasses the privilege escallation code in Member_GroupSet
|
|
||||||
$adminGroup
|
|
||||||
->DirectMembers()
|
|
||||||
->add($admin);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $admin;
|
/**
|
||||||
}
|
* Check if the passed password matches the stored one (if the member is not locked out).
|
||||||
|
*
|
||||||
/**
|
* @param string $password
|
||||||
* Check if the passed password matches the stored one (if the member is not locked out).
|
* @return ValidationResult
|
||||||
*
|
*/
|
||||||
* @param string $password
|
|
||||||
* @return ValidationResult
|
|
||||||
*/
|
|
||||||
public function checkPassword($password)
|
public function checkPassword($password)
|
||||||
{
|
{
|
||||||
$result = $this->canLogIn();
|
$result = $this->canLogIn();
|
||||||
|
|
||||||
// Short-circuit the result upon failure, no further checks needed.
|
// Short-circuit the result upon failure, no further checks needed.
|
||||||
if (!$result->valid()) {
|
if (!$result->isValid()) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow default admin to login as self
|
// Allow default admin to login as self
|
||||||
if($this->isDefaultAdmin() && Security::check_default_admin($this->Email, $password)) {
|
if ($this->isDefaultAdmin() && Security::check_default_admin($this->Email, $password)) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check a password is set on this member
|
// Check a password is set on this member
|
||||||
if(empty($this->Password) && $this->exists()) {
|
if (empty($this->Password) && $this->exists()) {
|
||||||
$result->addError(_t('Member.NoPassword','There is no password on this member.'));
|
$result->addError(_t('Member.NoPassword', 'There is no password on this member.'));
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$e = PasswordEncryptor::create_for_algorithm($this->PasswordEncryption);
|
$e = PasswordEncryptor::create_for_algorithm($this->PasswordEncryption);
|
||||||
if(!$e->check($this->Password, $password, $this->Salt, $this)) {
|
if (!$e->check($this->Password, $password, $this->Salt, $this)) {
|
||||||
$result->addError(_t (
|
$result->addError(_t(
|
||||||
'Member.ERRORWRONGCRED',
|
'Member.ERRORWRONGCRED',
|
||||||
'The provided details don\'t seem to be correct. Please try again.'
|
'The provided details don\'t seem to be correct. Please try again.'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this user is the currently configured default admin
|
* Check if this user is the currently configured default admin
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isDefaultAdmin()
|
public function isDefaultAdmin()
|
||||||
{
|
{
|
||||||
return Security::has_default_admin()
|
return Security::has_default_admin()
|
||||||
&& $this->Email === Security::default_admin_username();
|
&& $this->Email === Security::default_admin_username();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a valid {@link ValidationResult} if this member can currently log in, or an invalid
|
* Returns a valid {@link ValidationResult} if this member can currently log in, or an invalid
|
||||||
* one with error messages to display if the member is locked out.
|
* one with error messages to display if the member is locked out.
|
||||||
*
|
*
|
||||||
* You can hook into this with a "canLogIn" method on an attached extension.
|
* You can hook into this with a "canLogIn" method on an attached extension.
|
||||||
*
|
*
|
||||||
* @return ValidationResult
|
* @return ValidationResult
|
||||||
*/
|
*/
|
||||||
public function canLogIn()
|
public function canLogIn()
|
||||||
{
|
{
|
||||||
$result = ValidationResult::create();
|
$result = ValidationResult::create();
|
||||||
|
|
||||||
if($this->isLockedOut()) {
|
if ($this->isLockedOut()) {
|
||||||
$result->addError(
|
$result->addError(
|
||||||
_t(
|
_t(
|
||||||
'Member.ERRORLOCKEDOUT2',
|
'Member.ERRORLOCKEDOUT2',
|
||||||
'Your account has been temporarily disabled because of too many failed attempts at ' .
|
'Your account has been temporarily disabled because of too many failed attempts at ' .
|
||||||
'logging in. Please try again in {count} minutes.',
|
'logging in. Please try again in {count} minutes.',
|
||||||
null,
|
null,
|
||||||
array('count' => $this->config()->lock_out_delay_mins)
|
array('count' => $this->config()->lock_out_delay_mins)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->extend('canLogIn', $result);
|
$this->extend('canLogIn', $result);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this user is locked out
|
* Returns true if this user is locked out
|
||||||
*/
|
*/
|
||||||
public function isLockedOut()
|
public function isLockedOut()
|
||||||
{
|
{
|
||||||
return $this->LockedOutUntil && DBDatetime::now()->Format('U') < strtotime($this->LockedOutUntil);
|
return $this->LockedOutUntil && DBDatetime::now()->Format('U') < strtotime($this->LockedOutUntil);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regenerate the session_id.
|
* Regenerate the session_id.
|
||||||
* This wrapper is here to make it easier to disable calls to session_regenerate_id(), should you need to.
|
* This wrapper is here to make it easier to disable calls to session_regenerate_id(), should you need to.
|
||||||
* They have caused problems in certain
|
* They have caused problems in certain
|
||||||
* quirky problems (such as using the Windmill 0.3.6 proxy).
|
* quirky problems (such as using the Windmill 0.3.6 proxy).
|
||||||
*/
|
*/
|
||||||
public static function session_regenerate_id()
|
public static function session_regenerate_id()
|
||||||
{
|
{
|
||||||
if (!self::config()->session_regenerate_id) {
|
if (!self::config()->session_regenerate_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This can be called via CLI during testing.
|
// This can be called via CLI during testing.
|
||||||
if (Director::is_cli()) {
|
if (Director::is_cli()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = '';
|
$file = '';
|
||||||
$line = '';
|
$line = '';
|
||||||
|
|
||||||
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
|
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
|
||||||
// There's nothing we can do about this, because it's an operating system function!
|
// There's nothing we can do about this, because it's an operating system function!
|
||||||
if (!headers_sent($file, $line)) {
|
if (!headers_sent($file, $line)) {
|
||||||
@session_regenerate_id(true);
|
@session_regenerate_id(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a {@link PasswordValidator} object to use to validate member's passwords.
|
* Set a {@link PasswordValidator} object to use to validate member's passwords.
|
||||||
*
|
*
|
||||||
* @param PasswordValidator $pv
|
* @param PasswordValidator $pv
|
||||||
*/
|
*/
|
||||||
public static function set_password_validator($pv)
|
public static function set_password_validator($pv)
|
||||||
{
|
{
|
||||||
self::$password_validator = $pv;
|
self::$password_validator = $pv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current {@link PasswordValidator}
|
* Returns the current {@link PasswordValidator}
|
||||||
*
|
*
|
||||||
* @return PasswordValidator
|
* @return PasswordValidator
|
||||||
*/
|
*/
|
||||||
public static function password_validator()
|
public static function password_validator()
|
||||||
{
|
{
|
||||||
return self::$password_validator;
|
return self::$password_validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function isPasswordExpired()
|
public function isPasswordExpired()
|
||||||
@ -444,176 +444,176 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
if (!$this->PasswordExpiry) {
|
if (!$this->PasswordExpiry) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return strtotime(date('Y-m-d')) >= strtotime($this->PasswordExpiry);
|
return strtotime(date('Y-m-d')) >= strtotime($this->PasswordExpiry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs this member in
|
* Logs this member in
|
||||||
*
|
*
|
||||||
* @param bool $remember If set to TRUE, the member will be logged in automatically the next time.
|
* @param bool $remember If set to TRUE, the member will be logged in automatically the next time.
|
||||||
*/
|
*/
|
||||||
public function logIn($remember = false)
|
public function logIn($remember = false)
|
||||||
{
|
{
|
||||||
$this->extend('beforeMemberLoggedIn');
|
$this->extend('beforeMemberLoggedIn');
|
||||||
|
|
||||||
self::session_regenerate_id();
|
self::session_regenerate_id();
|
||||||
|
|
||||||
Session::set("loggedInAs", $this->ID);
|
Session::set("loggedInAs", $this->ID);
|
||||||
// This lets apache rules detect whether the user has logged in
|
// This lets apache rules detect whether the user has logged in
|
||||||
if (Member::config()->login_marker_cookie) {
|
if (Member::config()->login_marker_cookie) {
|
||||||
Cookie::set(Member::config()->login_marker_cookie, 1, 0);
|
Cookie::set(Member::config()->login_marker_cookie, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Security::config()->autologin_enabled) {
|
if (Security::config()->autologin_enabled) {
|
||||||
// Cleans up any potential previous hash for this member on this device
|
// Cleans up any potential previous hash for this member on this device
|
||||||
if ($alcDevice = Cookie::get('alc_device')) {
|
if ($alcDevice = Cookie::get('alc_device')) {
|
||||||
RememberLoginHash::get()->filter('DeviceID', $alcDevice)->removeAll();
|
RememberLoginHash::get()->filter('DeviceID', $alcDevice)->removeAll();
|
||||||
}
|
}
|
||||||
if($remember) {
|
if ($remember) {
|
||||||
$rememberLoginHash = RememberLoginHash::generate($this);
|
$rememberLoginHash = RememberLoginHash::generate($this);
|
||||||
$tokenExpiryDays = Config::inst()->get(
|
$tokenExpiryDays = Config::inst()->get(
|
||||||
'SilverStripe\\Security\\RememberLoginHash',
|
'SilverStripe\\Security\\RememberLoginHash',
|
||||||
'token_expiry_days'
|
'token_expiry_days'
|
||||||
);
|
);
|
||||||
$deviceExpiryDays = Config::inst()->get(
|
$deviceExpiryDays = Config::inst()->get(
|
||||||
'SilverStripe\\Security\\RememberLoginHash',
|
'SilverStripe\\Security\\RememberLoginHash',
|
||||||
'device_expiry_days'
|
'device_expiry_days'
|
||||||
);
|
);
|
||||||
Cookie::set(
|
Cookie::set(
|
||||||
'alc_enc',
|
'alc_enc',
|
||||||
$this->ID . ':' . $rememberLoginHash->getToken(),
|
$this->ID . ':' . $rememberLoginHash->getToken(),
|
||||||
$tokenExpiryDays,
|
$tokenExpiryDays,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
Cookie::set('alc_device', $rememberLoginHash->DeviceID, $deviceExpiryDays, null, null, null, true);
|
Cookie::set('alc_device', $rememberLoginHash->DeviceID, $deviceExpiryDays, null, null, null, true);
|
||||||
} else {
|
} else {
|
||||||
Cookie::set('alc_enc', null);
|
Cookie::set('alc_enc', null);
|
||||||
Cookie::set('alc_device', null);
|
Cookie::set('alc_device', null);
|
||||||
Cookie::force_expiry('alc_enc');
|
Cookie::force_expiry('alc_enc');
|
||||||
Cookie::force_expiry('alc_device');
|
Cookie::force_expiry('alc_device');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear the incorrect log-in count
|
// Clear the incorrect log-in count
|
||||||
$this->registerSuccessfulLogin();
|
$this->registerSuccessfulLogin();
|
||||||
|
|
||||||
$this->LockedOutUntil = null;
|
$this->LockedOutUntil = null;
|
||||||
|
|
||||||
$this->regenerateTempID();
|
$this->regenerateTempID();
|
||||||
|
|
||||||
$this->write();
|
$this->write();
|
||||||
|
|
||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$this->extend('memberLoggedIn');
|
$this->extend('memberLoggedIn');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger regeneration of TempID.
|
* Trigger regeneration of TempID.
|
||||||
*
|
*
|
||||||
* This should be performed any time the user presents their normal identification (normally Email)
|
* This should be performed any time the user presents their normal identification (normally Email)
|
||||||
* and is successfully authenticated.
|
* and is successfully authenticated.
|
||||||
*/
|
*/
|
||||||
public function regenerateTempID()
|
public function regenerateTempID()
|
||||||
{
|
{
|
||||||
$generator = new RandomGenerator();
|
$generator = new RandomGenerator();
|
||||||
$this->TempIDHash = $generator->randomToken('sha1');
|
$this->TempIDHash = $generator->randomToken('sha1');
|
||||||
$this->TempIDExpired = self::config()->temp_id_lifetime
|
$this->TempIDExpired = self::config()->temp_id_lifetime
|
||||||
? date('Y-m-d H:i:s', strtotime(DBDatetime::now()->getValue()) + self::config()->temp_id_lifetime)
|
? date('Y-m-d H:i:s', strtotime(DBDatetime::now()->getValue()) + self::config()->temp_id_lifetime)
|
||||||
: null;
|
: null;
|
||||||
$this->write();
|
$this->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the member ID logged in session actually
|
* Check if the member ID logged in session actually
|
||||||
* has a database record of the same ID. If there is
|
* has a database record of the same ID. If there is
|
||||||
* no logged in user, FALSE is returned anyway.
|
* no logged in user, FALSE is returned anyway.
|
||||||
*
|
*
|
||||||
* @return boolean TRUE record found FALSE no record found
|
* @return boolean TRUE record found FALSE no record found
|
||||||
*/
|
*/
|
||||||
public static function logged_in_session_exists()
|
public static function logged_in_session_exists()
|
||||||
{
|
{
|
||||||
if($id = Member::currentUserID()) {
|
if ($id = Member::currentUserID()) {
|
||||||
if($member = DataObject::get_by_id('SilverStripe\\Security\\Member', $id)) {
|
if ($member = DataObject::get_by_id('SilverStripe\\Security\\Member', $id)) {
|
||||||
if ($member->exists()) {
|
if ($member->exists()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log the user in if the "remember login" cookie is set
|
* Log the user in if the "remember login" cookie is set
|
||||||
*
|
*
|
||||||
* The <i>remember login token</i> will be changed on every successful
|
* The <i>remember login token</i> will be changed on every successful
|
||||||
* auto-login.
|
* auto-login.
|
||||||
*/
|
*/
|
||||||
public static function autoLogin()
|
public static function autoLogin()
|
||||||
{
|
{
|
||||||
// Don't bother trying this multiple times
|
// Don't bother trying this multiple times
|
||||||
if (!class_exists('SilverStripe\\Dev\\SapphireTest', false) || !SapphireTest::is_running_test()) {
|
if (!class_exists('SilverStripe\\Dev\\SapphireTest', false) || !SapphireTest::is_running_test()) {
|
||||||
self::$_already_tried_to_auto_log_in = true;
|
self::$_already_tried_to_auto_log_in = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Security::config()->autologin_enabled
|
if (!Security::config()->autologin_enabled
|
||||||
|| strpos(Cookie::get('alc_enc'), ':') === false
|
|| strpos(Cookie::get('alc_enc'), ':') === false
|
||||||
|| Session::get("loggedInAs")
|
|| Session::get("loggedInAs")
|
||||||
|| !Security::database_is_ready()
|
|| !Security::database_is_ready()
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(strpos(Cookie::get('alc_enc'), ':') && Cookie::get('alc_device') && !Session::get("loggedInAs")) {
|
if (strpos(Cookie::get('alc_enc'), ':') && Cookie::get('alc_device') && !Session::get("loggedInAs")) {
|
||||||
list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
|
list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
|
||||||
|
|
||||||
if (!$uid || !$token) {
|
if (!$uid || !$token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$deviceID = Cookie::get('alc_device');
|
$deviceID = Cookie::get('alc_device');
|
||||||
|
|
||||||
/** @var Member $member */
|
/** @var Member $member */
|
||||||
$member = Member::get()->byID($uid);
|
$member = Member::get()->byID($uid);
|
||||||
|
|
||||||
/** @var RememberLoginHash $rememberLoginHash */
|
/** @var RememberLoginHash $rememberLoginHash */
|
||||||
$rememberLoginHash = null;
|
$rememberLoginHash = null;
|
||||||
|
|
||||||
// check if autologin token matches
|
// check if autologin token matches
|
||||||
if($member) {
|
if ($member) {
|
||||||
$hash = $member->encryptWithUserSettings($token);
|
$hash = $member->encryptWithUserSettings($token);
|
||||||
$rememberLoginHash = RememberLoginHash::get()
|
$rememberLoginHash = RememberLoginHash::get()
|
||||||
->filter(array(
|
->filter(array(
|
||||||
'MemberID' => $member->ID,
|
'MemberID' => $member->ID,
|
||||||
'DeviceID' => $deviceID,
|
'DeviceID' => $deviceID,
|
||||||
'Hash' => $hash
|
'Hash' => $hash
|
||||||
))->first();
|
))->first();
|
||||||
if(!$rememberLoginHash) {
|
if (!$rememberLoginHash) {
|
||||||
$member = null;
|
$member = null;
|
||||||
} else {
|
} else {
|
||||||
// Check for expired token
|
// Check for expired token
|
||||||
$expiryDate = new DateTime($rememberLoginHash->ExpiryDate);
|
$expiryDate = new DateTime($rememberLoginHash->ExpiryDate);
|
||||||
$now = DBDatetime::now();
|
$now = DBDatetime::now();
|
||||||
$now = new DateTime($now->Rfc2822());
|
$now = new DateTime($now->Rfc2822());
|
||||||
if ($now > $expiryDate) {
|
if ($now > $expiryDate) {
|
||||||
$member = null;
|
$member = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($member) {
|
if ($member) {
|
||||||
self::session_regenerate_id();
|
self::session_regenerate_id();
|
||||||
Session::set("loggedInAs", $member->ID);
|
Session::set("loggedInAs", $member->ID);
|
||||||
// This lets apache rules detect whether the user has logged in
|
// This lets apache rules detect whether the user has logged in
|
||||||
if(Member::config()->login_marker_cookie) {
|
if (Member::config()->login_marker_cookie) {
|
||||||
Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true);
|
Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($rememberLoginHash) {
|
if ($rememberLoginHash) {
|
||||||
$rememberLoginHash->renew();
|
$rememberLoginHash->renew();
|
||||||
$tokenExpiryDays = RememberLoginHash::config()->get('token_expiry_days');
|
$tokenExpiryDays = RememberLoginHash::config()->get('token_expiry_days');
|
||||||
Cookie::set(
|
Cookie::set(
|
||||||
'alc_enc',
|
'alc_enc',
|
||||||
$member->ID . ':' . $rememberLoginHash->getToken(),
|
$member->ID . ':' . $rememberLoginHash->getToken(),
|
||||||
@ -623,272 +623,272 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
false,
|
false,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$member->write();
|
$member->write();
|
||||||
|
|
||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$member->extend('memberAutoLoggedIn');
|
$member->extend('memberAutoLoggedIn');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs this member out.
|
* Logs this member out.
|
||||||
*/
|
*/
|
||||||
public function logOut()
|
public function logOut()
|
||||||
{
|
{
|
||||||
$this->extend('beforeMemberLoggedOut');
|
$this->extend('beforeMemberLoggedOut');
|
||||||
|
|
||||||
Session::clear("loggedInAs");
|
Session::clear("loggedInAs");
|
||||||
if (Member::config()->login_marker_cookie) {
|
if (Member::config()->login_marker_cookie) {
|
||||||
Cookie::set(Member::config()->login_marker_cookie, null, 0);
|
Cookie::set(Member::config()->login_marker_cookie, null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Session::destroy();
|
Session::destroy();
|
||||||
|
|
||||||
$this->extend('memberLoggedOut');
|
$this->extend('memberLoggedOut');
|
||||||
|
|
||||||
// Clears any potential previous hashes for this member
|
// Clears any potential previous hashes for this member
|
||||||
RememberLoginHash::clear($this, Cookie::get('alc_device'));
|
RememberLoginHash::clear($this, Cookie::get('alc_device'));
|
||||||
|
|
||||||
Cookie::set('alc_enc', null); // // Clear the Remember Me cookie
|
Cookie::set('alc_enc', null); // // Clear the Remember Me cookie
|
||||||
Cookie::force_expiry('alc_enc');
|
Cookie::force_expiry('alc_enc');
|
||||||
Cookie::set('alc_device', null);
|
Cookie::set('alc_device', null);
|
||||||
Cookie::force_expiry('alc_device');
|
Cookie::force_expiry('alc_device');
|
||||||
|
|
||||||
// Switch back to live in order to avoid infinite loops when
|
// Switch back to live in order to avoid infinite loops when
|
||||||
// redirecting to the login screen (if this login screen is versioned)
|
// redirecting to the login screen (if this login screen is versioned)
|
||||||
Session::clear('readingMode');
|
Session::clear('readingMode');
|
||||||
|
|
||||||
$this->write();
|
$this->write();
|
||||||
|
|
||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$this->extend('memberLoggedOut');
|
$this->extend('memberLoggedOut');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility for generating secure password hashes for this member.
|
* Utility for generating secure password hashes for this member.
|
||||||
*
|
*
|
||||||
* @param string $string
|
* @param string $string
|
||||||
* @return string
|
* @return string
|
||||||
* @throws PasswordEncryptor_NotFoundException
|
* @throws PasswordEncryptor_NotFoundException
|
||||||
*/
|
*/
|
||||||
public function encryptWithUserSettings($string)
|
public function encryptWithUserSettings($string)
|
||||||
{
|
{
|
||||||
if (!$string) {
|
if (!$string) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the algorithm or salt is not available, it means we are operating
|
// If the algorithm or salt is not available, it means we are operating
|
||||||
// on legacy account with unhashed password. Do not hash the string.
|
// on legacy account with unhashed password. Do not hash the string.
|
||||||
if (!$this->PasswordEncryption) {
|
if (!$this->PasswordEncryption) {
|
||||||
return $string;
|
return $string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We assume we have PasswordEncryption and Salt available here.
|
// We assume we have PasswordEncryption and Salt available here.
|
||||||
$e = PasswordEncryptor::create_for_algorithm($this->PasswordEncryption);
|
$e = PasswordEncryptor::create_for_algorithm($this->PasswordEncryption);
|
||||||
return $e->encrypt($string, $this->Salt);
|
return $e->encrypt($string, $this->Salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate an auto login token which can be used to reset the password,
|
* Generate an auto login token which can be used to reset the password,
|
||||||
* at the same time hashing it and storing in the database.
|
* at the same time hashing it and storing in the database.
|
||||||
*
|
*
|
||||||
* @param int $lifetime The lifetime of the auto login hash in days (by default 2 days)
|
* @param int $lifetime The lifetime of the auto login hash in days (by default 2 days)
|
||||||
*
|
*
|
||||||
* @returns string Token that should be passed to the client (but NOT persisted).
|
* @returns string Token that should be passed to the client (but NOT persisted).
|
||||||
*
|
*
|
||||||
* @todo Make it possible to handle database errors such as a "duplicate key" error
|
* @todo Make it possible to handle database errors such as a "duplicate key" error
|
||||||
*/
|
*/
|
||||||
public function generateAutologinTokenAndStoreHash($lifetime = 2)
|
public function generateAutologinTokenAndStoreHash($lifetime = 2)
|
||||||
{
|
{
|
||||||
do {
|
do {
|
||||||
$generator = new RandomGenerator();
|
$generator = new RandomGenerator();
|
||||||
$token = $generator->randomToken();
|
$token = $generator->randomToken();
|
||||||
$hash = $this->encryptWithUserSettings($token);
|
$hash = $this->encryptWithUserSettings($token);
|
||||||
} while(DataObject::get_one('SilverStripe\\Security\\Member', array(
|
} while (DataObject::get_one('SilverStripe\\Security\\Member', array(
|
||||||
'"Member"."AutoLoginHash"' => $hash
|
'"Member"."AutoLoginHash"' => $hash
|
||||||
)));
|
)));
|
||||||
|
|
||||||
$this->AutoLoginHash = $hash;
|
$this->AutoLoginHash = $hash;
|
||||||
$this->AutoLoginExpired = date('Y-m-d H:i:s', time() + (86400 * $lifetime));
|
$this->AutoLoginExpired = date('Y-m-d H:i:s', time() + (86400 * $lifetime));
|
||||||
|
|
||||||
$this->write();
|
$this->write();
|
||||||
|
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the token against the member.
|
* Check the token against the member.
|
||||||
*
|
*
|
||||||
* @param string $autologinToken
|
* @param string $autologinToken
|
||||||
*
|
*
|
||||||
* @returns bool Is token valid?
|
* @returns bool Is token valid?
|
||||||
*/
|
*/
|
||||||
public function validateAutoLoginToken($autologinToken)
|
public function validateAutoLoginToken($autologinToken)
|
||||||
{
|
{
|
||||||
$hash = $this->encryptWithUserSettings($autologinToken);
|
$hash = $this->encryptWithUserSettings($autologinToken);
|
||||||
$member = self::member_from_autologinhash($hash, false);
|
$member = self::member_from_autologinhash($hash, false);
|
||||||
return (bool)$member;
|
return (bool)$member;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the member for the auto login hash
|
* Return the member for the auto login hash
|
||||||
*
|
*
|
||||||
* @param string $hash The hash key
|
* @param string $hash The hash key
|
||||||
* @param bool $login Should the member be logged in?
|
* @param bool $login Should the member be logged in?
|
||||||
*
|
*
|
||||||
* @return Member the matching member, if valid
|
* @return Member the matching member, if valid
|
||||||
* @return Member
|
* @return Member
|
||||||
*/
|
*/
|
||||||
public static function member_from_autologinhash($hash, $login = false)
|
public static function member_from_autologinhash($hash, $login = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
$nowExpression = DB::get_conn()->now();
|
$nowExpression = DB::get_conn()->now();
|
||||||
/** @var Member $member */
|
/** @var Member $member */
|
||||||
$member = DataObject::get_one('SilverStripe\\Security\\Member', array(
|
$member = DataObject::get_one('SilverStripe\\Security\\Member', array(
|
||||||
"\"Member\".\"AutoLoginHash\"" => $hash,
|
"\"Member\".\"AutoLoginHash\"" => $hash,
|
||||||
"\"Member\".\"AutoLoginExpired\" > $nowExpression" // NOW() can't be parameterised
|
"\"Member\".\"AutoLoginExpired\" > $nowExpression" // NOW() can't be parameterised
|
||||||
));
|
));
|
||||||
|
|
||||||
if ($login && $member) {
|
if ($login && $member) {
|
||||||
$member->logIn();
|
$member->logIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $member;
|
return $member;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a member record with the given TempIDHash value
|
* Find a member record with the given TempIDHash value
|
||||||
*
|
*
|
||||||
* @param string $tempid
|
* @param string $tempid
|
||||||
* @return Member
|
* @return Member
|
||||||
*/
|
*/
|
||||||
public static function member_from_tempid($tempid)
|
public static function member_from_tempid($tempid)
|
||||||
{
|
{
|
||||||
$members = Member::get()
|
$members = Member::get()
|
||||||
->filter('TempIDHash', $tempid);
|
->filter('TempIDHash', $tempid);
|
||||||
|
|
||||||
// Exclude expired
|
// Exclude expired
|
||||||
if(static::config()->temp_id_lifetime) {
|
if (static::config()->temp_id_lifetime) {
|
||||||
$members = $members->filter('TempIDExpired:GreaterThan', DBDatetime::now()->getValue());
|
$members = $members->filter('TempIDExpired:GreaterThan', DBDatetime::now()->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $members->first();
|
return $members->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the fields for the member form - used in the registration/profile module.
|
* Returns the fields for the member form - used in the registration/profile module.
|
||||||
* It should return fields that are editable by the admin and the logged-in user.
|
* It should return fields that are editable by the admin and the logged-in user.
|
||||||
*
|
*
|
||||||
* @return FieldList Returns a {@link FieldList} containing the fields for
|
* @return FieldList Returns a {@link FieldList} containing the fields for
|
||||||
* the member form.
|
* the member form.
|
||||||
*/
|
*/
|
||||||
public function getMemberFormFields()
|
public function getMemberFormFields()
|
||||||
{
|
{
|
||||||
$fields = parent::getFrontEndFields();
|
$fields = parent::getFrontEndFields();
|
||||||
|
|
||||||
$fields->replaceField('Password', $this->getMemberPasswordField());
|
$fields->replaceField('Password', $this->getMemberPasswordField());
|
||||||
|
|
||||||
$fields->replaceField('Locale', new DropdownField (
|
$fields->replaceField('Locale', new DropdownField(
|
||||||
'Locale',
|
'Locale',
|
||||||
$this->fieldLabel('Locale'),
|
$this->fieldLabel('Locale'),
|
||||||
i18n::get_existing_translations()
|
i18n::get_existing_translations()
|
||||||
));
|
));
|
||||||
|
|
||||||
$fields->removeByName(static::config()->hidden_fields);
|
$fields->removeByName(static::config()->hidden_fields);
|
||||||
$fields->removeByName('FailedLoginCount');
|
$fields->removeByName('FailedLoginCount');
|
||||||
|
|
||||||
|
|
||||||
$this->extend('updateMemberFormFields', $fields);
|
$this->extend('updateMemberFormFields', $fields);
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds "Change / Create Password" field for this member
|
* Builds "Change / Create Password" field for this member
|
||||||
*
|
*
|
||||||
* @return ConfirmedPasswordField
|
* @return ConfirmedPasswordField
|
||||||
*/
|
*/
|
||||||
public function getMemberPasswordField()
|
public function getMemberPasswordField()
|
||||||
{
|
{
|
||||||
$editingPassword = $this->isInDB();
|
$editingPassword = $this->isInDB();
|
||||||
$label = $editingPassword
|
$label = $editingPassword
|
||||||
? _t('Member.EDIT_PASSWORD', 'New Password')
|
? _t('Member.EDIT_PASSWORD', 'New Password')
|
||||||
: $this->fieldLabel('Password');
|
: $this->fieldLabel('Password');
|
||||||
/** @var ConfirmedPasswordField $password */
|
/** @var ConfirmedPasswordField $password */
|
||||||
$password = ConfirmedPasswordField::create(
|
$password = ConfirmedPasswordField::create(
|
||||||
'Password',
|
'Password',
|
||||||
$label,
|
$label,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
$editingPassword
|
$editingPassword
|
||||||
);
|
);
|
||||||
|
|
||||||
// If editing own password, require confirmation of existing
|
// If editing own password, require confirmation of existing
|
||||||
if($editingPassword && $this->ID == Member::currentUserID()) {
|
if ($editingPassword && $this->ID == Member::currentUserID()) {
|
||||||
$password->setRequireExistingPassword(true);
|
$password->setRequireExistingPassword(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$password->setCanBeEmpty(true);
|
$password->setCanBeEmpty(true);
|
||||||
$this->extend('updateMemberPasswordField', $password);
|
$this->extend('updateMemberPasswordField', $password);
|
||||||
return $password;
|
return $password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link RequiredFields} instance for the Member object. This
|
* Returns the {@link RequiredFields} instance for the Member object. This
|
||||||
* Validator is used when saving a {@link CMSProfileController} or added to
|
* Validator is used when saving a {@link CMSProfileController} or added to
|
||||||
* any form responsible for saving a users data.
|
* any form responsible for saving a users data.
|
||||||
*
|
*
|
||||||
* To customize the required fields, add a {@link DataExtension} to member
|
* To customize the required fields, add a {@link DataExtension} to member
|
||||||
* calling the `updateValidator()` method.
|
* calling the `updateValidator()` method.
|
||||||
*
|
*
|
||||||
* @return Member_Validator
|
* @return Member_Validator
|
||||||
*/
|
*/
|
||||||
public function getValidator()
|
public function getValidator()
|
||||||
{
|
{
|
||||||
$validator = Injector::inst()->create('SilverStripe\\Security\\Member_Validator');
|
$validator = Injector::inst()->create('SilverStripe\\Security\\Member_Validator');
|
||||||
$validator->setForMember($this);
|
$validator->setForMember($this);
|
||||||
$this->extend('updateValidator', $validator);
|
$this->extend('updateValidator', $validator);
|
||||||
|
|
||||||
return $validator;
|
return $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current logged in user
|
* Returns the current logged in user
|
||||||
*
|
*
|
||||||
* @return Member
|
* @return Member
|
||||||
*/
|
*/
|
||||||
public static function currentUser()
|
public static function currentUser()
|
||||||
{
|
{
|
||||||
$id = Member::currentUserID();
|
$id = Member::currentUserID();
|
||||||
|
|
||||||
if($id) {
|
if ($id) {
|
||||||
return DataObject::get_by_id('SilverStripe\\Security\\Member', $id);
|
return DataObject::get_by_id('SilverStripe\\Security\\Member', $id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the ID of the current logged in user
|
* Get the ID of the current logged in user
|
||||||
*
|
*
|
||||||
* @return int Returns the ID of the current logged in user or 0.
|
* @return int Returns the ID of the current logged in user or 0.
|
||||||
*/
|
*/
|
||||||
public static function currentUserID()
|
public static function currentUserID()
|
||||||
{
|
{
|
||||||
$id = Session::get("loggedInAs");
|
$id = Session::get("loggedInAs");
|
||||||
if(!$id && !self::$_already_tried_to_auto_log_in) {
|
if (!$id && !self::$_already_tried_to_auto_log_in) {
|
||||||
self::autoLogin();
|
self::autoLogin();
|
||||||
$id = Session::get("loggedInAs");
|
$id = Session::get("loggedInAs");
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_numeric($id) ? $id : 0;
|
return is_numeric($id) ? $id : 0;
|
||||||
}
|
}
|
||||||
private static $_already_tried_to_auto_log_in = false;
|
private static $_already_tried_to_auto_log_in = false;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate a random password, with randomiser to kick in if there's no words file on the
|
* Generate a random password, with randomiser to kick in if there's no words file on the
|
||||||
* filesystem.
|
* filesystem.
|
||||||
*
|
*
|
||||||
@ -896,178 +896,178 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
*/
|
*/
|
||||||
public static function create_new_password()
|
public static function create_new_password()
|
||||||
{
|
{
|
||||||
$words = Config::inst()->get('SilverStripe\\Security\\Security', 'word_list');
|
$words = Config::inst()->get('SilverStripe\\Security\\Security', 'word_list');
|
||||||
|
|
||||||
if($words && file_exists($words)) {
|
if ($words && file_exists($words)) {
|
||||||
$words = file($words);
|
$words = file($words);
|
||||||
|
|
||||||
list($usec, $sec) = explode(' ', microtime());
|
list($usec, $sec) = explode(' ', microtime());
|
||||||
srand($sec + ((float) $usec * 100000));
|
srand($sec + ((float) $usec * 100000));
|
||||||
|
|
||||||
$word = trim($words[rand(0,sizeof($words)-1)]);
|
$word = trim($words[rand(0, sizeof($words)-1)]);
|
||||||
$number = rand(10,999);
|
$number = rand(10, 999);
|
||||||
|
|
||||||
return $word . $number;
|
return $word . $number;
|
||||||
} else {
|
} else {
|
||||||
$random = rand();
|
$random = rand();
|
||||||
$string = md5($random);
|
$string = md5($random);
|
||||||
$output = substr($string, 0, 8);
|
$output = substr($string, 0, 8);
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler called before writing to the database.
|
* Event handler called before writing to the database.
|
||||||
*/
|
*/
|
||||||
public function onBeforeWrite()
|
public function onBeforeWrite()
|
||||||
{
|
{
|
||||||
if ($this->SetPassword) {
|
if ($this->SetPassword) {
|
||||||
$this->Password = $this->SetPassword;
|
$this->Password = $this->SetPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a member with the same "unique identifier" already exists with a different ID, don't allow merging.
|
// If a member with the same "unique identifier" already exists with a different ID, don't allow merging.
|
||||||
// Note: This does not a full replacement for safeguards in the controller layer (e.g. in a registration form),
|
// Note: This does not a full replacement for safeguards in the controller layer (e.g. in a registration form),
|
||||||
// but rather a last line of defense against data inconsistencies.
|
// but rather a last line of defense against data inconsistencies.
|
||||||
$identifierField = Member::config()->unique_identifier_field;
|
$identifierField = Member::config()->unique_identifier_field;
|
||||||
if($this->$identifierField) {
|
if ($this->$identifierField) {
|
||||||
// Note: Same logic as Member_Validator class
|
// Note: Same logic as Member_Validator class
|
||||||
$filter = array("\"$identifierField\"" => $this->$identifierField);
|
$filter = array("\"$identifierField\"" => $this->$identifierField);
|
||||||
if($this->ID) {
|
if ($this->ID) {
|
||||||
$filter[] = array('"Member"."ID" <> ?' => $this->ID);
|
$filter[] = array('"Member"."ID" <> ?' => $this->ID);
|
||||||
}
|
}
|
||||||
$existingRecord = DataObject::get_one('SilverStripe\\Security\\Member', $filter);
|
$existingRecord = DataObject::get_one('SilverStripe\\Security\\Member', $filter);
|
||||||
|
|
||||||
if($existingRecord) {
|
if ($existingRecord) {
|
||||||
throw new ValidationException(ValidationResult::create()->adderror(_t(
|
throw new ValidationException(_t(
|
||||||
'Member.ValidationIdentifierFailed',
|
'Member.ValidationIdentifierFailed',
|
||||||
'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))',
|
'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))',
|
||||||
'Values in brackets show "fieldname = value", usually denoting an existing email address',
|
'Values in brackets show "fieldname = value", usually denoting an existing email address',
|
||||||
array(
|
array(
|
||||||
'id' => $existingRecord->ID,
|
'id' => $existingRecord->ID,
|
||||||
'name' => $identifierField,
|
'name' => $identifierField,
|
||||||
'value' => $this->$identifierField
|
'value' => $this->$identifierField
|
||||||
)
|
)
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't send emails out on dev/tests sites to prevent accidentally spamming users.
|
// We don't send emails out on dev/tests sites to prevent accidentally spamming users.
|
||||||
// However, if TestMailer is in use this isn't a risk.
|
// However, if TestMailer is in use this isn't a risk.
|
||||||
if ((Director::isLive() || Email::mailer() instanceof TestMailer)
|
if ((Director::isLive() || Email::mailer() instanceof TestMailer)
|
||||||
&& $this->isChanged('Password')
|
&& $this->isChanged('Password')
|
||||||
&& $this->record['Password']
|
&& $this->record['Password']
|
||||||
&& $this->config()->notify_password_change
|
&& $this->config()->notify_password_change
|
||||||
) {
|
) {
|
||||||
/** @var Email $e */
|
/** @var Email $e */
|
||||||
$e = Email::create();
|
$e = Email::create();
|
||||||
$e->setSubject(_t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'));
|
$e->setSubject(_t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'));
|
||||||
$e->setTemplate('ChangePasswordEmail');
|
$e->setTemplate('ChangePasswordEmail');
|
||||||
$e->populateTemplate($this);
|
$e->populateTemplate($this);
|
||||||
$e->setTo($this->Email);
|
$e->setTo($this->Email);
|
||||||
$e->send();
|
$e->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The test on $this->ID is used for when records are initially created.
|
// The test on $this->ID is used for when records are initially created.
|
||||||
// Note that this only works with cleartext passwords, as we can't rehash
|
// Note that this only works with cleartext passwords, as we can't rehash
|
||||||
// existing passwords.
|
// existing passwords.
|
||||||
if((!$this->ID && $this->Password) || $this->isChanged('Password')) {
|
if ((!$this->ID && $this->Password) || $this->isChanged('Password')) {
|
||||||
//reset salt so that it gets regenerated - this will invalidate any persistant login cookies
|
//reset salt so that it gets regenerated - this will invalidate any persistant login cookies
|
||||||
// or other information encrypted with this Member's settings (see self::encryptWithUserSettings)
|
// or other information encrypted with this Member's settings (see self::encryptWithUserSettings)
|
||||||
$this->Salt = '';
|
$this->Salt = '';
|
||||||
// Password was changed: encrypt the password according the settings
|
// Password was changed: encrypt the password according the settings
|
||||||
$encryption_details = Security::encrypt_password(
|
$encryption_details = Security::encrypt_password(
|
||||||
$this->Password, // this is assumed to be cleartext
|
$this->Password, // this is assumed to be cleartext
|
||||||
$this->Salt,
|
$this->Salt,
|
||||||
($this->PasswordEncryption) ?
|
($this->PasswordEncryption) ?
|
||||||
$this->PasswordEncryption : Security::config()->password_encryption_algorithm,
|
$this->PasswordEncryption : Security::config()->password_encryption_algorithm,
|
||||||
$this
|
$this
|
||||||
);
|
);
|
||||||
|
|
||||||
// Overwrite the Password property with the hashed value
|
// Overwrite the Password property with the hashed value
|
||||||
$this->Password = $encryption_details['password'];
|
$this->Password = $encryption_details['password'];
|
||||||
$this->Salt = $encryption_details['salt'];
|
$this->Salt = $encryption_details['salt'];
|
||||||
$this->PasswordEncryption = $encryption_details['algorithm'];
|
$this->PasswordEncryption = $encryption_details['algorithm'];
|
||||||
|
|
||||||
// If we haven't manually set a password expiry
|
// If we haven't manually set a password expiry
|
||||||
if(!$this->isChanged('PasswordExpiry')) {
|
if (!$this->isChanged('PasswordExpiry')) {
|
||||||
// then set it for us
|
// then set it for us
|
||||||
if(self::config()->password_expiry_days) {
|
if (self::config()->password_expiry_days) {
|
||||||
$this->PasswordExpiry = date('Y-m-d', time() + 86400 * self::config()->password_expiry_days);
|
$this->PasswordExpiry = date('Y-m-d', time() + 86400 * self::config()->password_expiry_days);
|
||||||
} else {
|
} else {
|
||||||
$this->PasswordExpiry = null;
|
$this->PasswordExpiry = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save locale
|
// save locale
|
||||||
if(!$this->Locale) {
|
if (!$this->Locale) {
|
||||||
$this->Locale = i18n::get_locale();
|
$this->Locale = i18n::get_locale();
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::onBeforeWrite();
|
parent::onBeforeWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onAfterWrite()
|
public function onAfterWrite()
|
||||||
{
|
{
|
||||||
parent::onAfterWrite();
|
parent::onAfterWrite();
|
||||||
|
|
||||||
Permission::flush_permission_cache();
|
Permission::flush_permission_cache();
|
||||||
|
|
||||||
if($this->isChanged('Password')) {
|
if ($this->isChanged('Password')) {
|
||||||
MemberPassword::log($this);
|
MemberPassword::log($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onAfterDelete()
|
public function onAfterDelete()
|
||||||
{
|
{
|
||||||
parent::onAfterDelete();
|
parent::onAfterDelete();
|
||||||
|
|
||||||
//prevent orphaned records remaining in the DB
|
//prevent orphaned records remaining in the DB
|
||||||
$this->deletePasswordLogs();
|
$this->deletePasswordLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the MemberPassword objects that are associated to this user
|
* Delete the MemberPassword objects that are associated to this user
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
protected function deletePasswordLogs()
|
protected function deletePasswordLogs()
|
||||||
{
|
{
|
||||||
foreach ($this->LoggedPasswords() as $password) {
|
foreach ($this->LoggedPasswords() as $password) {
|
||||||
$password->delete();
|
$password->delete();
|
||||||
$password->destroy();
|
$password->destroy();
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter out admin groups to avoid privilege escalation,
|
* Filter out admin groups to avoid privilege escalation,
|
||||||
* If any admin groups are requested, deny the whole save operation.
|
* If any admin groups are requested, deny the whole save operation.
|
||||||
*
|
*
|
||||||
* @param array $ids Database IDs of Group records
|
* @param array $ids Database IDs of Group records
|
||||||
* @return bool True if the change can be accepted
|
* @return bool True if the change can be accepted
|
||||||
*/
|
*/
|
||||||
public function onChangeGroups($ids)
|
public function onChangeGroups($ids)
|
||||||
{
|
{
|
||||||
// unless the current user is an admin already OR the logged in user is an admin
|
// unless the current user is an admin already OR the logged in user is an admin
|
||||||
if(Permission::check('ADMIN') || Permission::checkMember($this, 'ADMIN')) {
|
if (Permission::check('ADMIN') || Permission::checkMember($this, 'ADMIN')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no admin groups in this set then it's ok
|
// If there are no admin groups in this set then it's ok
|
||||||
$adminGroups = Permission::get_groups_by_permission('ADMIN');
|
$adminGroups = Permission::get_groups_by_permission('ADMIN');
|
||||||
$adminGroupIDs = ($adminGroups) ? $adminGroups->column('ID') : array();
|
$adminGroupIDs = ($adminGroups) ? $adminGroups->column('ID') : array();
|
||||||
return count(array_intersect($ids, $adminGroupIDs)) == 0;
|
return count(array_intersect($ids, $adminGroupIDs)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the member is in one of the given groups.
|
* Check if the member is in one of the given groups.
|
||||||
*
|
*
|
||||||
* @param array|SS_List $groups Collection of {@link Group} DataObjects to check
|
* @param array|SS_List $groups Collection of {@link Group} DataObjects to check
|
||||||
* @param boolean $strict Only determine direct group membership if set to true (Default: false)
|
* @param boolean $strict Only determine direct group membership if set to true (Default: false)
|
||||||
* @return bool Returns TRUE if the member is in one of the given groups, otherwise FALSE.
|
* @return bool Returns TRUE if the member is in one of the given groups, otherwise FALSE.
|
||||||
*/
|
*/
|
||||||
public function inGroups($groups, $strict = false)
|
public function inGroups($groups, $strict = false)
|
||||||
{
|
{
|
||||||
if ($groups) {
|
if ($groups) {
|
||||||
@ -1076,608 +1076,608 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the member is in the given group or any parent groups.
|
* Check if the member is in the given group or any parent groups.
|
||||||
*
|
*
|
||||||
* @param int|Group|string $group Group instance, Group Code or ID
|
* @param int|Group|string $group Group instance, Group Code or ID
|
||||||
* @param boolean $strict Only determine direct group membership if set to TRUE (Default: FALSE)
|
* @param boolean $strict Only determine direct group membership if set to TRUE (Default: FALSE)
|
||||||
* @return bool Returns TRUE if the member is in the given group, otherwise FALSE.
|
* @return bool Returns TRUE if the member is in the given group, otherwise FALSE.
|
||||||
*/
|
*/
|
||||||
public function inGroup($group, $strict = false)
|
public function inGroup($group, $strict = false)
|
||||||
{
|
{
|
||||||
if(is_numeric($group)) {
|
if (is_numeric($group)) {
|
||||||
$groupCheckObj = DataObject::get_by_id('SilverStripe\\Security\\Group', $group);
|
$groupCheckObj = DataObject::get_by_id('SilverStripe\\Security\\Group', $group);
|
||||||
} elseif(is_string($group)) {
|
} elseif (is_string($group)) {
|
||||||
$groupCheckObj = DataObject::get_one('SilverStripe\\Security\\Group', array(
|
$groupCheckObj = DataObject::get_one('SilverStripe\\Security\\Group', array(
|
||||||
'"Group"."Code"' => $group
|
'"Group"."Code"' => $group
|
||||||
));
|
));
|
||||||
} elseif($group instanceof Group) {
|
} elseif ($group instanceof Group) {
|
||||||
$groupCheckObj = $group;
|
$groupCheckObj = $group;
|
||||||
} else {
|
} else {
|
||||||
user_error('Member::inGroup(): Wrong format for $group parameter', E_USER_ERROR);
|
user_error('Member::inGroup(): Wrong format for $group parameter', E_USER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$groupCheckObj) {
|
if (!$groupCheckObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$groupCandidateObjs = ($strict) ? $this->getManyManyComponents("Groups") : $this->Groups();
|
$groupCandidateObjs = ($strict) ? $this->getManyManyComponents("Groups") : $this->Groups();
|
||||||
if ($groupCandidateObjs) {
|
if ($groupCandidateObjs) {
|
||||||
foreach ($groupCandidateObjs as $groupCandidateObj) {
|
foreach ($groupCandidateObjs as $groupCandidateObj) {
|
||||||
if ($groupCandidateObj->ID == $groupCheckObj->ID) {
|
if ($groupCandidateObj->ID == $groupCheckObj->ID) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the member to a group. This will create the group if the given
|
* Adds the member to a group. This will create the group if the given
|
||||||
* group code does not return a valid group object.
|
* group code does not return a valid group object.
|
||||||
*
|
*
|
||||||
* @param string $groupcode
|
* @param string $groupcode
|
||||||
* @param string $title Title of the group
|
* @param string $title Title of the group
|
||||||
*/
|
*/
|
||||||
public function addToGroupByCode($groupcode, $title = "")
|
public function addToGroupByCode($groupcode, $title = "")
|
||||||
{
|
{
|
||||||
$group = DataObject::get_one('SilverStripe\\Security\\Group', array(
|
$group = DataObject::get_one('SilverStripe\\Security\\Group', array(
|
||||||
'"Group"."Code"' => $groupcode
|
'"Group"."Code"' => $groupcode
|
||||||
));
|
));
|
||||||
|
|
||||||
if($group) {
|
if ($group) {
|
||||||
$this->Groups()->add($group);
|
$this->Groups()->add($group);
|
||||||
} else {
|
} else {
|
||||||
if (!$title) {
|
if (!$title) {
|
||||||
$title = $groupcode;
|
$title = $groupcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
$group = new Group();
|
$group = new Group();
|
||||||
$group->Code = $groupcode;
|
$group->Code = $groupcode;
|
||||||
$group->Title = $title;
|
$group->Title = $title;
|
||||||
$group->write();
|
$group->write();
|
||||||
|
|
||||||
$this->Groups()->add($group);
|
$this->Groups()->add($group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a member from a group.
|
* Removes a member from a group.
|
||||||
*
|
*
|
||||||
* @param string $groupcode
|
* @param string $groupcode
|
||||||
*/
|
*/
|
||||||
public function removeFromGroupByCode($groupcode)
|
public function removeFromGroupByCode($groupcode)
|
||||||
{
|
{
|
||||||
$group = Group::get()->filter(array('Code' => $groupcode))->first();
|
$group = Group::get()->filter(array('Code' => $groupcode))->first();
|
||||||
|
|
||||||
if($group) {
|
if ($group) {
|
||||||
$this->Groups()->remove($group);
|
$this->Groups()->remove($group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $columns Column names on the Member record to show in {@link getTitle()}.
|
* @param array $columns Column names on the Member record to show in {@link getTitle()}.
|
||||||
* @param String $sep Separator
|
* @param String $sep Separator
|
||||||
*/
|
*/
|
||||||
public static function set_title_columns($columns, $sep = ' ')
|
public static function set_title_columns($columns, $sep = ' ')
|
||||||
{
|
{
|
||||||
if (!is_array($columns)) {
|
if (!is_array($columns)) {
|
||||||
$columns = array($columns);
|
$columns = array($columns);
|
||||||
}
|
}
|
||||||
self::config()->title_format = array('columns' => $columns, 'sep' => $sep);
|
self::config()->title_format = array('columns' => $columns, 'sep' => $sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------- HELPER METHODS -----------------------------------//
|
//------------------- HELPER METHODS -----------------------------------//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the complete name of the member, by default in the format "<Surname>, <FirstName>".
|
* Get the complete name of the member, by default in the format "<Surname>, <FirstName>".
|
||||||
* Falls back to showing either field on its own.
|
* Falls back to showing either field on its own.
|
||||||
*
|
*
|
||||||
* You can overload this getter with {@link set_title_format()}
|
* You can overload this getter with {@link set_title_format()}
|
||||||
* and {@link set_title_sql()}.
|
* and {@link set_title_sql()}.
|
||||||
*
|
*
|
||||||
* @return string Returns the first- and surname of the member. If the ID
|
* @return string Returns the first- and surname of the member. If the ID
|
||||||
* of the member is equal 0, only the surname is returned.
|
* of the member is equal 0, only the surname is returned.
|
||||||
*/
|
*/
|
||||||
public function getTitle()
|
public function getTitle()
|
||||||
{
|
{
|
||||||
$format = $this->config()->title_format;
|
$format = $this->config()->title_format;
|
||||||
if ($format) {
|
if ($format) {
|
||||||
$values = array();
|
$values = array();
|
||||||
foreach($format['columns'] as $col) {
|
foreach ($format['columns'] as $col) {
|
||||||
$values[] = $this->getField($col);
|
$values[] = $this->getField($col);
|
||||||
}
|
}
|
||||||
return join($format['sep'], $values);
|
return join($format['sep'], $values);
|
||||||
}
|
}
|
||||||
if ($this->getField('ID') === 0) {
|
if ($this->getField('ID') === 0) {
|
||||||
return $this->getField('Surname');
|
return $this->getField('Surname');
|
||||||
} else {
|
} else {
|
||||||
if($this->getField('Surname') && $this->getField('FirstName')){
|
if ($this->getField('Surname') && $this->getField('FirstName')) {
|
||||||
return $this->getField('Surname') . ', ' . $this->getField('FirstName');
|
return $this->getField('Surname') . ', ' . $this->getField('FirstName');
|
||||||
}elseif($this->getField('Surname')){
|
} elseif ($this->getField('Surname')) {
|
||||||
return $this->getField('Surname');
|
return $this->getField('Surname');
|
||||||
}elseif($this->getField('FirstName')){
|
} elseif ($this->getField('FirstName')) {
|
||||||
return $this->getField('FirstName');
|
return $this->getField('FirstName');
|
||||||
}else{
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a SQL CONCAT() fragment suitable for a SELECT statement.
|
* Return a SQL CONCAT() fragment suitable for a SELECT statement.
|
||||||
* Useful for custom queries which assume a certain member title format.
|
* Useful for custom queries which assume a certain member title format.
|
||||||
*
|
*
|
||||||
* @return String SQL
|
* @return String SQL
|
||||||
*/
|
*/
|
||||||
public static function get_title_sql()
|
public static function get_title_sql()
|
||||||
{
|
{
|
||||||
// This should be abstracted to SSDatabase concatOperator or similar.
|
// This should be abstracted to SSDatabase concatOperator or similar.
|
||||||
$op = (DB::get_conn() instanceof MSSQLDatabase) ? " + " : " || ";
|
$op = (DB::get_conn() instanceof MSSQLDatabase) ? " + " : " || ";
|
||||||
|
|
||||||
// Get title_format with fallback to default
|
// Get title_format with fallback to default
|
||||||
$format = static::config()->title_format;
|
$format = static::config()->title_format;
|
||||||
if (!$format) {
|
if (!$format) {
|
||||||
$format = [
|
$format = [
|
||||||
'columns' => ['Surname', 'FirstName'],
|
'columns' => ['Surname', 'FirstName'],
|
||||||
'sep' => ' ',
|
'sep' => ' ',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$columnsWithTablename = array();
|
$columnsWithTablename = array();
|
||||||
foreach($format['columns'] as $column) {
|
foreach ($format['columns'] as $column) {
|
||||||
$columnsWithTablename[] = static::getSchema()->sqlColumnForField(__CLASS__, $column);
|
$columnsWithTablename[] = static::getSchema()->sqlColumnForField(__CLASS__, $column);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sepSQL = Convert::raw2sql($format['sep'], true);
|
$sepSQL = Convert::raw2sql($format['sep'], true);
|
||||||
return "(".join(" $op $sepSQL $op ", $columnsWithTablename).")";
|
return "(".join(" $op $sepSQL $op ", $columnsWithTablename).")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the complete name of the member
|
* Get the complete name of the member
|
||||||
*
|
*
|
||||||
* @return string Returns the first- and surname of the member.
|
* @return string Returns the first- and surname of the member.
|
||||||
*/
|
*/
|
||||||
public function getName()
|
public function getName()
|
||||||
{
|
{
|
||||||
return ($this->Surname) ? trim($this->FirstName . ' ' . $this->Surname) : $this->FirstName;
|
return ($this->Surname) ? trim($this->FirstName . ' ' . $this->Surname) : $this->FirstName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set first- and surname
|
* Set first- and surname
|
||||||
*
|
*
|
||||||
* This method assumes that the last part of the name is the surname, e.g.
|
* This method assumes that the last part of the name is the surname, e.g.
|
||||||
* <i>A B C</i> will result in firstname <i>A B</i> and surname <i>C</i>
|
* <i>A B C</i> will result in firstname <i>A B</i> and surname <i>C</i>
|
||||||
*
|
*
|
||||||
* @param string $name The name
|
* @param string $name The name
|
||||||
*/
|
*/
|
||||||
public function setName($name)
|
public function setName($name)
|
||||||
{
|
{
|
||||||
$nameParts = explode(' ', $name);
|
$nameParts = explode(' ', $name);
|
||||||
$this->Surname = array_pop($nameParts);
|
$this->Surname = array_pop($nameParts);
|
||||||
$this->FirstName = join(' ', $nameParts);
|
$this->FirstName = join(' ', $nameParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for {@link setName}
|
* Alias for {@link setName}
|
||||||
*
|
*
|
||||||
* @param string $name The name
|
* @param string $name The name
|
||||||
* @see setName()
|
* @see setName()
|
||||||
*/
|
*/
|
||||||
public function splitName($name)
|
public function splitName($name)
|
||||||
{
|
{
|
||||||
return $this->setName($name);
|
return $this->setName($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the default getter for DateFormat so the
|
* Override the default getter for DateFormat so the
|
||||||
* default format for the user's locale is used
|
* default format for the user's locale is used
|
||||||
* if the user has not defined their own.
|
* if the user has not defined their own.
|
||||||
*
|
*
|
||||||
* @return string ISO date format
|
* @return string ISO date format
|
||||||
*/
|
*/
|
||||||
public function getDateFormat()
|
public function getDateFormat()
|
||||||
{
|
{
|
||||||
if($this->getField('DateFormat')) {
|
if ($this->getField('DateFormat')) {
|
||||||
return $this->getField('DateFormat');
|
return $this->getField('DateFormat');
|
||||||
} else {
|
} else {
|
||||||
return i18n::config()->get('date_format');
|
return i18n::config()->get('date_format');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the default getter for TimeFormat so the
|
* Override the default getter for TimeFormat so the
|
||||||
* default format for the user's locale is used
|
* default format for the user's locale is used
|
||||||
* if the user has not defined their own.
|
* if the user has not defined their own.
|
||||||
*
|
*
|
||||||
* @return string ISO date format
|
* @return string ISO date format
|
||||||
*/
|
*/
|
||||||
public function getTimeFormat()
|
public function getTimeFormat()
|
||||||
{
|
{
|
||||||
if($this->getField('TimeFormat')) {
|
if ($this->getField('TimeFormat')) {
|
||||||
return $this->getField('TimeFormat');
|
return $this->getField('TimeFormat');
|
||||||
} else {
|
} else {
|
||||||
return i18n::config()->get('time_format');
|
return i18n::config()->get('time_format');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------//
|
//---------------------------------------------------------------------//
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a "many-to-many" map that holds for all members their group memberships,
|
* Get a "many-to-many" map that holds for all members their group memberships,
|
||||||
* including any parent groups where membership is implied.
|
* including any parent groups where membership is implied.
|
||||||
* Use {@link DirectGroups()} to only retrieve the group relations without inheritance.
|
* Use {@link DirectGroups()} to only retrieve the group relations without inheritance.
|
||||||
*
|
*
|
||||||
* @todo Push all this logic into Member_GroupSet's getIterator()?
|
* @todo Push all this logic into Member_GroupSet's getIterator()?
|
||||||
* @return Member_Groupset
|
* @return Member_Groupset
|
||||||
*/
|
*/
|
||||||
public function Groups()
|
public function Groups()
|
||||||
{
|
{
|
||||||
$groups = Member_GroupSet::create('SilverStripe\\Security\\Group', 'Group_Members', 'GroupID', 'MemberID');
|
$groups = Member_GroupSet::create('SilverStripe\\Security\\Group', 'Group_Members', 'GroupID', 'MemberID');
|
||||||
$groups = $groups->forForeignID($this->ID);
|
$groups = $groups->forForeignID($this->ID);
|
||||||
|
|
||||||
$this->extend('updateGroups', $groups);
|
$this->extend('updateGroups', $groups);
|
||||||
|
|
||||||
return $groups;
|
return $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ManyManyList
|
* @return ManyManyList
|
||||||
*/
|
*/
|
||||||
public function DirectGroups()
|
public function DirectGroups()
|
||||||
{
|
{
|
||||||
return $this->getManyManyComponents('Groups');
|
return $this->getManyManyComponents('Groups');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a member SQLMap of members in specific groups
|
* Get a member SQLMap of members in specific groups
|
||||||
*
|
*
|
||||||
* If no $groups is passed, all members will be returned
|
* If no $groups is passed, all members will be returned
|
||||||
*
|
*
|
||||||
* @param mixed $groups - takes a SS_List, an array or a single Group.ID
|
* @param mixed $groups - takes a SS_List, an array or a single Group.ID
|
||||||
* @return Map Returns an Map that returns all Member data.
|
* @return Map Returns an Map that returns all Member data.
|
||||||
*/
|
*/
|
||||||
public static function map_in_groups($groups = null)
|
public static function map_in_groups($groups = null)
|
||||||
{
|
{
|
||||||
$groupIDList = array();
|
$groupIDList = array();
|
||||||
|
|
||||||
if($groups instanceof SS_List) {
|
if ($groups instanceof SS_List) {
|
||||||
foreach( $groups as $group ) {
|
foreach ($groups as $group) {
|
||||||
$groupIDList[] = $group->ID;
|
$groupIDList[] = $group->ID;
|
||||||
}
|
}
|
||||||
} elseif(is_array($groups)) {
|
} elseif (is_array($groups)) {
|
||||||
$groupIDList = $groups;
|
$groupIDList = $groups;
|
||||||
} elseif($groups) {
|
} elseif ($groups) {
|
||||||
$groupIDList[] = $groups;
|
$groupIDList[] = $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No groups, return all Members
|
// No groups, return all Members
|
||||||
if(!$groupIDList) {
|
if (!$groupIDList) {
|
||||||
return Member::get()->sort(array('Surname'=>'ASC', 'FirstName'=>'ASC'))->map();
|
return Member::get()->sort(array('Surname'=>'ASC', 'FirstName'=>'ASC'))->map();
|
||||||
}
|
}
|
||||||
|
|
||||||
$membersList = new ArrayList();
|
$membersList = new ArrayList();
|
||||||
// This is a bit ineffective, but follow the ORM style
|
// This is a bit ineffective, but follow the ORM style
|
||||||
foreach(Group::get()->byIDs($groupIDList) as $group) {
|
foreach (Group::get()->byIDs($groupIDList) as $group) {
|
||||||
$membersList->merge($group->Members());
|
$membersList->merge($group->Members());
|
||||||
}
|
}
|
||||||
|
|
||||||
$membersList->removeDuplicates('ID');
|
$membersList->removeDuplicates('ID');
|
||||||
return $membersList->map();
|
return $membersList->map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a map of all members in the groups given that have CMS permissions
|
* Get a map of all members in the groups given that have CMS permissions
|
||||||
*
|
*
|
||||||
* If no groups are passed, all groups with CMS permissions will be used.
|
* If no groups are passed, all groups with CMS permissions will be used.
|
||||||
*
|
*
|
||||||
* @param array $groups Groups to consider or NULL to use all groups with
|
* @param array $groups Groups to consider or NULL to use all groups with
|
||||||
* CMS permissions.
|
* CMS permissions.
|
||||||
* @return Map Returns a map of all members in the groups given that
|
* @return Map Returns a map of all members in the groups given that
|
||||||
* have CMS permissions.
|
* have CMS permissions.
|
||||||
*/
|
*/
|
||||||
public static function mapInCMSGroups($groups = null)
|
public static function mapInCMSGroups($groups = null)
|
||||||
{
|
{
|
||||||
if(!$groups || $groups->Count() == 0) {
|
if (!$groups || $groups->Count() == 0) {
|
||||||
$perms = array('ADMIN', 'CMS_ACCESS_AssetAdmin');
|
$perms = array('ADMIN', 'CMS_ACCESS_AssetAdmin');
|
||||||
|
|
||||||
if (class_exists('SilverStripe\\CMS\\Controllers\\CMSMain')) {
|
if (class_exists('SilverStripe\\CMS\\Controllers\\CMSMain')) {
|
||||||
$cmsPerms = CMSMain::singleton()->providePermissions();
|
$cmsPerms = CMSMain::singleton()->providePermissions();
|
||||||
} else {
|
} else {
|
||||||
$cmsPerms = LeftAndMain::singleton()->providePermissions();
|
$cmsPerms = LeftAndMain::singleton()->providePermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($cmsPerms)) {
|
if (!empty($cmsPerms)) {
|
||||||
$perms = array_unique(array_merge($perms, array_keys($cmsPerms)));
|
$perms = array_unique(array_merge($perms, array_keys($cmsPerms)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$permsClause = DB::placeholders($perms);
|
$permsClause = DB::placeholders($perms);
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
$groups = Group::get()
|
$groups = Group::get()
|
||||||
->innerJoin("Permission", '"Permission"."GroupID" = "Group"."ID"')
|
->innerJoin("Permission", '"Permission"."GroupID" = "Group"."ID"')
|
||||||
->where(array(
|
->where(array(
|
||||||
"\"Permission\".\"Code\" IN ($permsClause)" => $perms
|
"\"Permission\".\"Code\" IN ($permsClause)" => $perms
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$groupIDList = array();
|
$groupIDList = array();
|
||||||
|
|
||||||
if($groups instanceof SS_List) {
|
if ($groups instanceof SS_List) {
|
||||||
foreach($groups as $group) {
|
foreach ($groups as $group) {
|
||||||
$groupIDList[] = $group->ID;
|
$groupIDList[] = $group->ID;
|
||||||
}
|
}
|
||||||
} elseif(is_array($groups)) {
|
} elseif (is_array($groups)) {
|
||||||
$groupIDList = $groups;
|
$groupIDList = $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
$members = Member::get()
|
$members = Member::get()
|
||||||
->innerJoin("Group_Members", '"Group_Members"."MemberID" = "Member"."ID"')
|
->innerJoin("Group_Members", '"Group_Members"."MemberID" = "Member"."ID"')
|
||||||
->innerJoin("Group", '"Group"."ID" = "Group_Members"."GroupID"');
|
->innerJoin("Group", '"Group"."ID" = "Group_Members"."GroupID"');
|
||||||
if($groupIDList) {
|
if ($groupIDList) {
|
||||||
$groupClause = DB::placeholders($groupIDList);
|
$groupClause = DB::placeholders($groupIDList);
|
||||||
$members = $members->where(array(
|
$members = $members->where(array(
|
||||||
"\"Group\".\"ID\" IN ($groupClause)" => $groupIDList
|
"\"Group\".\"ID\" IN ($groupClause)" => $groupIDList
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $members->sort('"Member"."Surname", "Member"."FirstName"')->map();
|
return $members->sort('"Member"."Surname", "Member"."FirstName"')->map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the groups in which the member is NOT in
|
* Get the groups in which the member is NOT in
|
||||||
*
|
*
|
||||||
* When passed an array of groups, and a component set of groups, this
|
* When passed an array of groups, and a component set of groups, this
|
||||||
* function will return the array of groups the member is NOT in.
|
* function will return the array of groups the member is NOT in.
|
||||||
*
|
*
|
||||||
* @param array $groupList An array of group code names.
|
* @param array $groupList An array of group code names.
|
||||||
* @param array $memberGroups A component set of groups (if set to NULL,
|
* @param array $memberGroups A component set of groups (if set to NULL,
|
||||||
* $this->groups() will be used)
|
* $this->groups() will be used)
|
||||||
* @return array Groups in which the member is NOT in.
|
* @return array Groups in which the member is NOT in.
|
||||||
*/
|
*/
|
||||||
public function memberNotInGroups($groupList, $memberGroups = null)
|
public function memberNotInGroups($groupList, $memberGroups = null)
|
||||||
{
|
{
|
||||||
if (!$memberGroups) {
|
if (!$memberGroups) {
|
||||||
$memberGroups = $this->Groups();
|
$memberGroups = $this->Groups();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($memberGroups as $group) {
|
foreach ($memberGroups as $group) {
|
||||||
if(in_array($group->Code, $groupList)) {
|
if (in_array($group->Code, $groupList)) {
|
||||||
$index = array_search($group->Code, $groupList);
|
$index = array_search($group->Code, $groupList);
|
||||||
unset($groupList[$index]);
|
unset($groupList[$index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $groupList;
|
return $groupList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@link FieldList} of fields that would appropriate for editing
|
* Return a {@link FieldList} of fields that would appropriate for editing
|
||||||
* this member.
|
* this member.
|
||||||
*
|
*
|
||||||
* @return FieldList Return a FieldList of fields that would appropriate for
|
* @return FieldList Return a FieldList of fields that would appropriate for
|
||||||
* editing this member.
|
* editing this member.
|
||||||
*/
|
*/
|
||||||
public function getCMSFields()
|
public function getCMSFields()
|
||||||
{
|
{
|
||||||
require_once 'Zend/Date.php';
|
require_once 'Zend/Date.php';
|
||||||
|
|
||||||
$self = $this;
|
$self = $this;
|
||||||
$this->beforeUpdateCMSFields(function(FieldList $fields) use ($self) {
|
$this->beforeUpdateCMSFields(function (FieldList $fields) use ($self) {
|
||||||
/** @var FieldList $mainFields */
|
/** @var FieldList $mainFields */
|
||||||
$mainFields = $fields->fieldByName("Root")->fieldByName("Main")->getChildren();
|
$mainFields = $fields->fieldByName("Root")->fieldByName("Main")->getChildren();
|
||||||
|
|
||||||
// Build change password field
|
// Build change password field
|
||||||
$mainFields->replaceField('Password', $self->getMemberPasswordField());
|
$mainFields->replaceField('Password', $self->getMemberPasswordField());
|
||||||
|
|
||||||
$mainFields->replaceField('Locale', new DropdownField(
|
$mainFields->replaceField('Locale', new DropdownField(
|
||||||
"Locale",
|
"Locale",
|
||||||
_t('Member.INTERFACELANG', "Interface Language", 'Language of the CMS'),
|
_t('Member.INTERFACELANG', "Interface Language", 'Language of the CMS'),
|
||||||
i18n::get_existing_translations()
|
i18n::get_existing_translations()
|
||||||
));
|
));
|
||||||
$mainFields->removeByName($self->config()->hidden_fields);
|
$mainFields->removeByName($self->config()->hidden_fields);
|
||||||
|
|
||||||
if( ! $self->config()->lock_out_after_incorrect_logins) {
|
if (! $self->config()->lock_out_after_incorrect_logins) {
|
||||||
$mainFields->removeByName('FailedLoginCount');
|
$mainFields->removeByName('FailedLoginCount');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Groups relation will get us into logical conflicts because
|
// Groups relation will get us into logical conflicts because
|
||||||
// Members are displayed within group edit form in SecurityAdmin
|
// Members are displayed within group edit form in SecurityAdmin
|
||||||
$fields->removeByName('Groups');
|
$fields->removeByName('Groups');
|
||||||
|
|
||||||
// Members shouldn't be able to directly view/edit logged passwords
|
// Members shouldn't be able to directly view/edit logged passwords
|
||||||
$fields->removeByName('LoggedPasswords');
|
$fields->removeByName('LoggedPasswords');
|
||||||
|
|
||||||
$fields->removeByName('RememberLoginHashes');
|
$fields->removeByName('RememberLoginHashes');
|
||||||
|
|
||||||
if(Permission::check('EDIT_PERMISSIONS')) {
|
if (Permission::check('EDIT_PERMISSIONS')) {
|
||||||
$groupsMap = array();
|
$groupsMap = array();
|
||||||
foreach(Group::get() as $group) {
|
foreach (Group::get() as $group) {
|
||||||
// Listboxfield values are escaped, use ASCII char instead of »
|
// Listboxfield values are escaped, use ASCII char instead of »
|
||||||
$groupsMap[$group->ID] = $group->getBreadcrumbs(' > ');
|
$groupsMap[$group->ID] = $group->getBreadcrumbs(' > ');
|
||||||
}
|
}
|
||||||
asort($groupsMap);
|
asort($groupsMap);
|
||||||
$fields->addFieldToTab(
|
$fields->addFieldToTab(
|
||||||
'Root.Main',
|
'Root.Main',
|
||||||
ListboxField::create('DirectGroups', singleton('SilverStripe\\Security\\Group')->i18n_plural_name())
|
ListboxField::create('DirectGroups', singleton('SilverStripe\\Security\\Group')->i18n_plural_name())
|
||||||
->setSource($groupsMap)
|
->setSource($groupsMap)
|
||||||
->setAttribute(
|
->setAttribute(
|
||||||
'data-placeholder',
|
'data-placeholder',
|
||||||
_t('Member.ADDGROUP', 'Add group', 'Placeholder text for a dropdown')
|
_t('Member.ADDGROUP', 'Add group', 'Placeholder text for a dropdown')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Add permission field (readonly to avoid complicated group assignment logic).
|
// Add permission field (readonly to avoid complicated group assignment logic).
|
||||||
// This should only be available for existing records, as new records start
|
// This should only be available for existing records, as new records start
|
||||||
// with no permissions until they have a group assignment anyway.
|
// with no permissions until they have a group assignment anyway.
|
||||||
if($self->ID) {
|
if ($self->ID) {
|
||||||
$permissionsField = new PermissionCheckboxSetField_Readonly(
|
$permissionsField = new PermissionCheckboxSetField_Readonly(
|
||||||
'Permissions',
|
'Permissions',
|
||||||
false,
|
false,
|
||||||
'SilverStripe\\Security\\Permission',
|
'SilverStripe\\Security\\Permission',
|
||||||
'GroupID',
|
'GroupID',
|
||||||
// we don't want parent relationships, they're automatically resolved in the field
|
// we don't want parent relationships, they're automatically resolved in the field
|
||||||
$self->getManyManyComponents('Groups')
|
$self->getManyManyComponents('Groups')
|
||||||
);
|
);
|
||||||
$fields->findOrMakeTab('Root.Permissions', singleton('SilverStripe\\Security\\Permission')->i18n_plural_name());
|
$fields->findOrMakeTab('Root.Permissions', singleton('SilverStripe\\Security\\Permission')->i18n_plural_name());
|
||||||
$fields->addFieldToTab('Root.Permissions', $permissionsField);
|
$fields->addFieldToTab('Root.Permissions', $permissionsField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$permissionsTab = $fields->fieldByName("Root")->fieldByName('Permissions');
|
$permissionsTab = $fields->fieldByName("Root")->fieldByName('Permissions');
|
||||||
if ($permissionsTab) {
|
if ($permissionsTab) {
|
||||||
$permissionsTab->addExtraClass('readonly');
|
$permissionsTab->addExtraClass('readonly');
|
||||||
}
|
}
|
||||||
|
|
||||||
$defaultDateFormat = Zend_Locale_Format::getDateFormat(new Zend_Locale($self->Locale));
|
$defaultDateFormat = Zend_Locale_Format::getDateFormat(new Zend_Locale($self->Locale));
|
||||||
$dateFormatMap = array(
|
$dateFormatMap = array(
|
||||||
'MMM d, yyyy' => Zend_Date::now()->toString('MMM d, yyyy'),
|
'MMM d, yyyy' => Zend_Date::now()->toString('MMM d, yyyy'),
|
||||||
'yyyy/MM/dd' => Zend_Date::now()->toString('yyyy/MM/dd'),
|
'yyyy/MM/dd' => Zend_Date::now()->toString('yyyy/MM/dd'),
|
||||||
'MM/dd/yyyy' => Zend_Date::now()->toString('MM/dd/yyyy'),
|
'MM/dd/yyyy' => Zend_Date::now()->toString('MM/dd/yyyy'),
|
||||||
'dd/MM/yyyy' => Zend_Date::now()->toString('dd/MM/yyyy'),
|
'dd/MM/yyyy' => Zend_Date::now()->toString('dd/MM/yyyy'),
|
||||||
);
|
);
|
||||||
$dateFormatMap[$defaultDateFormat] = Zend_Date::now()->toString($defaultDateFormat)
|
$dateFormatMap[$defaultDateFormat] = Zend_Date::now()->toString($defaultDateFormat)
|
||||||
. sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
|
. sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
|
||||||
$mainFields->push(
|
$mainFields->push(
|
||||||
$dateFormatField = new MemberDatetimeOptionsetField(
|
$dateFormatField = new MemberDatetimeOptionsetField(
|
||||||
'DateFormat',
|
'DateFormat',
|
||||||
$self->fieldLabel('DateFormat'),
|
$self->fieldLabel('DateFormat'),
|
||||||
$dateFormatMap
|
$dateFormatMap
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$formatClass = get_class($dateFormatField);
|
$formatClass = get_class($dateFormatField);
|
||||||
$dateFormatField->setValue($self->DateFormat);
|
$dateFormatField->setValue($self->DateFormat);
|
||||||
$dateTemplate = SSViewer::get_templates_by_class($formatClass, '_description_date', $formatClass);
|
$dateTemplate = SSViewer::get_templates_by_class($formatClass, '_description_date', $formatClass);
|
||||||
$dateFormatField->setDescriptionTemplate($dateTemplate);
|
$dateFormatField->setDescriptionTemplate($dateTemplate);
|
||||||
|
|
||||||
$defaultTimeFormat = Zend_Locale_Format::getTimeFormat(new Zend_Locale($self->Locale));
|
$defaultTimeFormat = Zend_Locale_Format::getTimeFormat(new Zend_Locale($self->Locale));
|
||||||
$timeFormatMap = array(
|
$timeFormatMap = array(
|
||||||
'h:mm a' => Zend_Date::now()->toString('h:mm a'),
|
'h:mm a' => Zend_Date::now()->toString('h:mm a'),
|
||||||
'H:mm' => Zend_Date::now()->toString('H:mm'),
|
'H:mm' => Zend_Date::now()->toString('H:mm'),
|
||||||
);
|
);
|
||||||
$timeFormatMap[$defaultTimeFormat] = Zend_Date::now()->toString($defaultTimeFormat)
|
$timeFormatMap[$defaultTimeFormat] = Zend_Date::now()->toString($defaultTimeFormat)
|
||||||
. sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
|
. sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
|
||||||
$mainFields->push(
|
$mainFields->push(
|
||||||
$timeFormatField = new MemberDatetimeOptionsetField(
|
$timeFormatField = new MemberDatetimeOptionsetField(
|
||||||
'TimeFormat',
|
'TimeFormat',
|
||||||
$self->fieldLabel('TimeFormat'),
|
$self->fieldLabel('TimeFormat'),
|
||||||
$timeFormatMap
|
$timeFormatMap
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$timeFormatField->setValue($self->TimeFormat);
|
$timeFormatField->setValue($self->TimeFormat);
|
||||||
$timeTemplate = SSViewer::get_templates_by_class($formatClass,'_description_time', $formatClass);
|
$timeTemplate = SSViewer::get_templates_by_class($formatClass, '_description_time', $formatClass);
|
||||||
$timeFormatField->setDescriptionTemplate($timeTemplate);
|
$timeFormatField->setDescriptionTemplate($timeTemplate);
|
||||||
});
|
});
|
||||||
|
|
||||||
return parent::getCMSFields();
|
return parent::getCMSFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $includerelations Indicate if the labels returned include relation fields
|
* @param bool $includerelations Indicate if the labels returned include relation fields
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function fieldLabels($includerelations = true)
|
public function fieldLabels($includerelations = true)
|
||||||
{
|
{
|
||||||
$labels = parent::fieldLabels($includerelations);
|
$labels = parent::fieldLabels($includerelations);
|
||||||
|
|
||||||
$labels['FirstName'] = _t('Member.FIRSTNAME', 'First Name');
|
$labels['FirstName'] = _t('Member.FIRSTNAME', 'First Name');
|
||||||
$labels['Surname'] = _t('Member.SURNAME', 'Surname');
|
$labels['Surname'] = _t('Member.SURNAME', 'Surname');
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
$labels['Email'] = _t('Member.EMAIL', 'Email');
|
$labels['Email'] = _t('Member.EMAIL', 'Email');
|
||||||
$labels['Password'] = _t('Member.db_Password', 'Password');
|
$labels['Password'] = _t('Member.db_Password', 'Password');
|
||||||
$labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', 'Password expiry date');
|
$labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', 'Password expiry date');
|
||||||
$labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', 'Security related date');
|
$labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', 'Security related date');
|
||||||
$labels['Locale'] = _t('Member.db_Locale', 'Interface Locale');
|
$labels['Locale'] = _t('Member.db_Locale', 'Interface Locale');
|
||||||
$labels['DateFormat'] = _t('Member.DATEFORMAT', 'Date format');
|
$labels['DateFormat'] = _t('Member.DATEFORMAT', 'Date format');
|
||||||
$labels['TimeFormat'] = _t('Member.TIMEFORMAT', 'Time format');
|
$labels['TimeFormat'] = _t('Member.TIMEFORMAT', 'Time format');
|
||||||
if($includerelations){
|
if ($includerelations) {
|
||||||
$labels['Groups'] = _t(
|
$labels['Groups'] = _t(
|
||||||
'Member.belongs_many_many_Groups',
|
'Member.belongs_many_many_Groups',
|
||||||
'Groups',
|
'Groups',
|
||||||
'Security Groups this member belongs to'
|
'Security Groups this member belongs to'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return $labels;
|
return $labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Users can view their own record.
|
* Users can view their own record.
|
||||||
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions.
|
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions.
|
||||||
* This is likely to be customized for social sites etc. with a looser permission model.
|
* This is likely to be customized for social sites etc. with a looser permission model.
|
||||||
*
|
*
|
||||||
* @param Member $member
|
* @param Member $member
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function canView($member = null)
|
public function canView($member = null)
|
||||||
{
|
{
|
||||||
//get member
|
//get member
|
||||||
if(!($member instanceof Member)) {
|
if (!($member instanceof Member)) {
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
}
|
}
|
||||||
//check for extensions, we do this first as they can overrule everything
|
//check for extensions, we do this first as they can overrule everything
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
$extended = $this->extendedCan(__FUNCTION__, $member);
|
||||||
if($extended !== null) {
|
if ($extended !== null) {
|
||||||
return $extended;
|
return $extended;
|
||||||
}
|
}
|
||||||
|
|
||||||
//need to be logged in and/or most checks below rely on $member being a Member
|
//need to be logged in and/or most checks below rely on $member being a Member
|
||||||
if(!$member) {
|
if (!$member) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// members can usually view their own record
|
// members can usually view their own record
|
||||||
if($this->ID == $member->ID) {
|
if ($this->ID == $member->ID) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//standard check
|
//standard check
|
||||||
return Permission::checkMember($member, 'CMS_ACCESS_SecurityAdmin');
|
return Permission::checkMember($member, 'CMS_ACCESS_SecurityAdmin');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Users can edit their own record.
|
* Users can edit their own record.
|
||||||
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions
|
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions
|
||||||
*
|
*
|
||||||
* @param Member $member
|
* @param Member $member
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function canEdit($member = null)
|
public function canEdit($member = null)
|
||||||
{
|
{
|
||||||
//get member
|
//get member
|
||||||
if(!($member instanceof Member)) {
|
if (!($member instanceof Member)) {
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
}
|
}
|
||||||
//check for extensions, we do this first as they can overrule everything
|
//check for extensions, we do this first as they can overrule everything
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
$extended = $this->extendedCan(__FUNCTION__, $member);
|
||||||
if($extended !== null) {
|
if ($extended !== null) {
|
||||||
return $extended;
|
return $extended;
|
||||||
}
|
}
|
||||||
|
|
||||||
//need to be logged in and/or most checks below rely on $member being a Member
|
//need to be logged in and/or most checks below rely on $member being a Member
|
||||||
if(!$member) {
|
if (!$member) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: we should not allow for an non-Admin to edit an Admin
|
// HACK: we should not allow for an non-Admin to edit an Admin
|
||||||
if(!Permission::checkMember($member, 'ADMIN') && Permission::checkMember($this, 'ADMIN')) {
|
if (!Permission::checkMember($member, 'ADMIN') && Permission::checkMember($this, 'ADMIN')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// members can usually edit their own record
|
// members can usually edit their own record
|
||||||
if($this->ID == $member->ID) {
|
if ($this->ID == $member->ID) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//standard check
|
//standard check
|
||||||
@ -1686,36 +1686,36 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
/**
|
/**
|
||||||
* Users can edit their own record.
|
* Users can edit their own record.
|
||||||
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions
|
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions
|
||||||
*
|
*
|
||||||
* @param Member $member
|
* @param Member $member
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function canDelete($member = null)
|
public function canDelete($member = null)
|
||||||
{
|
{
|
||||||
if(!($member instanceof Member)) {
|
if (!($member instanceof Member)) {
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
}
|
}
|
||||||
//check for extensions, we do this first as they can overrule everything
|
//check for extensions, we do this first as they can overrule everything
|
||||||
$extended = $this->extendedCan(__FUNCTION__, $member);
|
$extended = $this->extendedCan(__FUNCTION__, $member);
|
||||||
if($extended !== null) {
|
if ($extended !== null) {
|
||||||
return $extended;
|
return $extended;
|
||||||
}
|
}
|
||||||
|
|
||||||
//need to be logged in and/or most checks below rely on $member being a Member
|
//need to be logged in and/or most checks below rely on $member being a Member
|
||||||
if(!$member) {
|
if (!$member) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Members are not allowed to remove themselves,
|
// Members are not allowed to remove themselves,
|
||||||
// since it would create inconsistencies in the admin UIs.
|
// since it would create inconsistencies in the admin UIs.
|
||||||
if($this->ID && $member->ID == $this->ID) {
|
if ($this->ID && $member->ID == $this->ID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: if you want to delete a member, you have to be a member yourself.
|
// HACK: if you want to delete a member, you have to be a member yourself.
|
||||||
// this is a hack because what this should do is to stop a user
|
// this is a hack because what this should do is to stop a user
|
||||||
// deleting a member who has more privileges (e.g. a non-Admin deleting an Admin)
|
// deleting a member who has more privileges (e.g. a non-Admin deleting an Admin)
|
||||||
if(Permission::checkMember($this, 'ADMIN')) {
|
if (Permission::checkMember($this, 'ADMIN')) {
|
||||||
if( ! Permission::checkMember($member, 'ADMIN')) {
|
if (! Permission::checkMember($member, 'ADMIN')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1723,111 +1723,111 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
return Permission::checkMember($member, 'CMS_ACCESS_SecurityAdmin');
|
return Permission::checkMember($member, 'CMS_ACCESS_SecurityAdmin');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate this member object.
|
* Validate this member object.
|
||||||
*/
|
*/
|
||||||
public function validate()
|
public function validate()
|
||||||
{
|
{
|
||||||
$valid = parent::validate();
|
$valid = parent::validate();
|
||||||
|
|
||||||
if(!$this->ID || $this->isChanged('Password')) {
|
if (!$this->ID || $this->isChanged('Password')) {
|
||||||
if($this->Password && self::$password_validator) {
|
if ($this->Password && self::$password_validator) {
|
||||||
$valid->combineAnd(self::$password_validator->validate($this->Password, $this));
|
$valid->combineAnd(self::$password_validator->validate($this->Password, $this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if((!$this->ID && $this->SetPassword) || $this->isChanged('SetPassword')) {
|
if ((!$this->ID && $this->SetPassword) || $this->isChanged('SetPassword')) {
|
||||||
if($this->SetPassword && self::$password_validator) {
|
if ($this->SetPassword && self::$password_validator) {
|
||||||
$valid->combineAnd(self::$password_validator->validate($this->SetPassword, $this));
|
$valid->combineAnd(self::$password_validator->validate($this->SetPassword, $this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $valid;
|
return $valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change password. This will cause rehashing according to
|
* Change password. This will cause rehashing according to
|
||||||
* the `PasswordEncryption` property.
|
* the `PasswordEncryption` property.
|
||||||
*
|
*
|
||||||
* @param string $password Cleartext password
|
* @param string $password Cleartext password
|
||||||
* @return ValidationResult
|
* @return ValidationResult
|
||||||
*/
|
*/
|
||||||
public function changePassword($password)
|
public function changePassword($password)
|
||||||
{
|
{
|
||||||
$this->Password = $password;
|
$this->Password = $password;
|
||||||
$valid = $this->validate();
|
$valid = $this->validate();
|
||||||
|
|
||||||
if($valid->valid()) {
|
if ($valid->isValid()) {
|
||||||
$this->AutoLoginHash = null;
|
$this->AutoLoginHash = null;
|
||||||
$this->write();
|
$this->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $valid;
|
return $valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell this member that someone made a failed attempt at logging in as them.
|
* Tell this member that someone made a failed attempt at logging in as them.
|
||||||
* This can be used to lock the user out temporarily if too many failed attempts are made.
|
* This can be used to lock the user out temporarily if too many failed attempts are made.
|
||||||
*/
|
*/
|
||||||
public function registerFailedLogin()
|
public function registerFailedLogin()
|
||||||
{
|
{
|
||||||
if(self::config()->lock_out_after_incorrect_logins) {
|
if (self::config()->lock_out_after_incorrect_logins) {
|
||||||
// Keep a tally of the number of failed log-ins so that we can lock people out
|
// Keep a tally of the number of failed log-ins so that we can lock people out
|
||||||
$this->FailedLoginCount = $this->FailedLoginCount + 1;
|
$this->FailedLoginCount = $this->FailedLoginCount + 1;
|
||||||
|
|
||||||
if($this->FailedLoginCount >= self::config()->lock_out_after_incorrect_logins) {
|
if ($this->FailedLoginCount >= self::config()->lock_out_after_incorrect_logins) {
|
||||||
$lockoutMins = self::config()->lock_out_delay_mins;
|
$lockoutMins = self::config()->lock_out_delay_mins;
|
||||||
$this->LockedOutUntil = date('Y-m-d H:i:s', DBDatetime::now()->Format('U') + $lockoutMins*60);
|
$this->LockedOutUntil = date('Y-m-d H:i:s', DBDatetime::now()->Format('U') + $lockoutMins*60);
|
||||||
$this->FailedLoginCount = 0;
|
$this->FailedLoginCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->extend('registerFailedLogin');
|
$this->extend('registerFailedLogin');
|
||||||
$this->write();
|
$this->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell this member that a successful login has been made
|
* Tell this member that a successful login has been made
|
||||||
*/
|
*/
|
||||||
public function registerSuccessfulLogin()
|
public function registerSuccessfulLogin()
|
||||||
{
|
{
|
||||||
if(self::config()->lock_out_after_incorrect_logins) {
|
if (self::config()->lock_out_after_incorrect_logins) {
|
||||||
// Forgive all past login failures
|
// Forgive all past login failures
|
||||||
$this->FailedLoginCount = 0;
|
$this->FailedLoginCount = 0;
|
||||||
$this->write();
|
$this->write();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the HtmlEditorConfig for this user to be used in the CMS.
|
* Get the HtmlEditorConfig for this user to be used in the CMS.
|
||||||
* This is set by the group. If multiple configurations are set,
|
* This is set by the group. If multiple configurations are set,
|
||||||
* the one with the highest priority wins.
|
* the one with the highest priority wins.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getHtmlEditorConfigForCMS()
|
public function getHtmlEditorConfigForCMS()
|
||||||
{
|
{
|
||||||
$currentName = '';
|
$currentName = '';
|
||||||
$currentPriority = 0;
|
$currentPriority = 0;
|
||||||
|
|
||||||
foreach($this->Groups() as $group) {
|
foreach ($this->Groups() as $group) {
|
||||||
$configName = $group->HtmlEditorConfig;
|
$configName = $group->HtmlEditorConfig;
|
||||||
if($configName) {
|
if ($configName) {
|
||||||
$config = HTMLEditorConfig::get($group->HtmlEditorConfig);
|
$config = HTMLEditorConfig::get($group->HtmlEditorConfig);
|
||||||
if($config && $config->getOption('priority') > $currentPriority) {
|
if ($config && $config->getOption('priority') > $currentPriority) {
|
||||||
$currentName = $configName;
|
$currentName = $configName;
|
||||||
$currentPriority = $config->getOption('priority');
|
$currentPriority = $config->getOption('priority');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If can't find a suitable editor, just default to cms
|
// If can't find a suitable editor, just default to cms
|
||||||
return $currentName ? $currentName : 'cms';
|
return $currentName ? $currentName : 'cms';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_template_global_variables()
|
public static function get_template_global_variables()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'CurrentMember' => 'currentUser',
|
'CurrentMember' => 'currentUser',
|
||||||
'currentUser',
|
'currentUser',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,208 +16,212 @@ use InvalidArgumentException;
|
|||||||
class MemberAuthenticator extends Authenticator
|
class MemberAuthenticator extends Authenticator
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains encryption algorithm identifiers.
|
* Contains encryption algorithm identifiers.
|
||||||
* If set, will migrate to new precision-safe password hashing
|
* If set, will migrate to new precision-safe password hashing
|
||||||
* upon login. See http://open.silverstripe.org/ticket/3004
|
* upon login. See http://open.silverstripe.org/ticket/3004
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $migrate_legacy_hashes = array(
|
private static $migrate_legacy_hashes = array(
|
||||||
'md5' => 'md5_v2.4',
|
'md5' => 'md5_v2.4',
|
||||||
'sha1' => 'sha1_v2.4'
|
'sha1' => 'sha1_v2.4'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to find and authenticate member if possible from the given data
|
* Attempt to find and authenticate member if possible from the given data
|
||||||
*
|
*
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @param Form $form
|
* @param Form $form
|
||||||
* @param bool &$success Success flag
|
* @param bool &$success Success flag
|
||||||
* @return Member Found member, regardless of successful login
|
* @return Member Found member, regardless of successful login
|
||||||
*/
|
*/
|
||||||
protected static function authenticate_member($data, $form, &$success)
|
protected static function authenticate_member($data, $form, &$success)
|
||||||
{
|
{
|
||||||
// Default success to false
|
// Default success to false
|
||||||
$success = false;
|
$success = false;
|
||||||
|
|
||||||
// Attempt to identify by temporary ID
|
// Attempt to identify by temporary ID
|
||||||
$member = null;
|
$member = null;
|
||||||
$email = null;
|
$email = null;
|
||||||
if(!empty($data['tempid'])) {
|
if (!empty($data['tempid'])) {
|
||||||
// Find user by tempid, in case they are re-validating an existing session
|
// Find user by tempid, in case they are re-validating an existing session
|
||||||
$member = Member::member_from_tempid($data['tempid']);
|
$member = Member::member_from_tempid($data['tempid']);
|
||||||
if ($member) {
|
if ($member) {
|
||||||
$email = $member->Email;
|
$email = $member->Email;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, get email from posted value instead
|
// Otherwise, get email from posted value instead
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
if(!$member && !empty($data['Email'])) {
|
if (!$member && !empty($data['Email'])) {
|
||||||
$email = $data['Email'];
|
$email = $data['Email'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check default login (see Security::setDefaultAdmin())
|
// Check default login (see Security::setDefaultAdmin())
|
||||||
$asDefaultAdmin = $email === Security::default_admin_username();
|
$asDefaultAdmin = $email === Security::default_admin_username();
|
||||||
if($asDefaultAdmin) {
|
if ($asDefaultAdmin) {
|
||||||
// If logging is as default admin, ensure record is setup correctly
|
// If logging is as default admin, ensure record is setup correctly
|
||||||
$member = Member::default_admin();
|
$member = Member::default_admin();
|
||||||
$success = !$member->isLockedOut() && Security::check_default_admin($email, $data['Password']);
|
$success = !$member->isLockedOut() && Security::check_default_admin($email, $data['Password']);
|
||||||
//protect against failed login
|
//protect against failed login
|
||||||
if($success) {
|
if ($success) {
|
||||||
return $member;
|
return $member;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to identify user by email
|
// Attempt to identify user by email
|
||||||
if(!$member && $email) {
|
if (!$member && $email) {
|
||||||
// Find user by email
|
// Find user by email
|
||||||
$member = Member::get()
|
$member = Member::get()
|
||||||
->filter(Member::config()->unique_identifier_field, $email)
|
->filter(Member::config()->unique_identifier_field, $email)
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate against member if possible
|
// Validate against member if possible
|
||||||
if($member && !$asDefaultAdmin) {
|
if ($member && !$asDefaultAdmin) {
|
||||||
$result = $member->checkPassword($data['Password']);
|
$result = $member->checkPassword($data['Password']);
|
||||||
$success = $result->valid();
|
$success = $result->isValid();
|
||||||
} else {
|
} else {
|
||||||
$result = ValidationResult::create()->addError(_t('Member.ERRORWRONGCRED'));
|
$result = ValidationResult::create()->addError(_t('Member.ERRORWRONGCRED'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit failure to member and form (if available)
|
// Emit failure to member and form (if available)
|
||||||
if(!$success) {
|
if (!$success) {
|
||||||
if($member) $member->registerFailedLogin();
|
if ($member) {
|
||||||
if($form) $form->setSessionValidationResult($result, true);
|
$member->registerFailedLogin();
|
||||||
} else {
|
}
|
||||||
|
if ($form) {
|
||||||
|
$form->setSessionValidationResult($result, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if ($member) {
|
if ($member) {
|
||||||
$member->registerSuccessfulLogin();
|
$member->registerSuccessfulLogin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $member;
|
return $member;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log login attempt
|
* Log login attempt
|
||||||
* TODO We could handle this with an extension
|
* TODO We could handle this with an extension
|
||||||
*
|
*
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @param Member $member
|
* @param Member $member
|
||||||
* @param bool $success
|
* @param bool $success
|
||||||
*/
|
*/
|
||||||
protected static function record_login_attempt($data, $member, $success)
|
protected static function record_login_attempt($data, $member, $success)
|
||||||
{
|
{
|
||||||
if (!Security::config()->login_recording) {
|
if (!Security::config()->login_recording) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check email is valid
|
// Check email is valid
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
$email = isset($data['Email']) ? $data['Email'] : null;
|
$email = isset($data['Email']) ? $data['Email'] : null;
|
||||||
if(is_array($email)) {
|
if (is_array($email)) {
|
||||||
throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): $email");
|
throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): $email");
|
||||||
}
|
}
|
||||||
|
|
||||||
$attempt = new LoginAttempt();
|
$attempt = new LoginAttempt();
|
||||||
if($success) {
|
if ($success) {
|
||||||
// successful login (member is existing with matching password)
|
// successful login (member is existing with matching password)
|
||||||
$attempt->MemberID = $member->ID;
|
$attempt->MemberID = $member->ID;
|
||||||
$attempt->Status = 'Success';
|
$attempt->Status = 'Success';
|
||||||
|
|
||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$member->extend('authenticated');
|
$member->extend('authenticated');
|
||||||
} else {
|
} else {
|
||||||
// Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
|
// Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
|
||||||
$attempt->Status = 'Failure';
|
$attempt->Status = 'Failure';
|
||||||
if($member) {
|
if ($member) {
|
||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$attempt->MemberID = $member->ID;
|
$attempt->MemberID = $member->ID;
|
||||||
$member->extend('authenticationFailed');
|
$member->extend('authenticationFailed');
|
||||||
} else {
|
} else {
|
||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
Member::singleton()->extend('authenticationFailedUnknownUser', $data);
|
Member::singleton()->extend('authenticationFailedUnknownUser', $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$attempt->Email = $email;
|
$attempt->Email = $email;
|
||||||
$attempt->IP = Controller::curr()->getRequest()->getIP();
|
$attempt->IP = Controller::curr()->getRequest()->getIP();
|
||||||
$attempt->write();
|
$attempt->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to authenticate an user
|
* Method to authenticate an user
|
||||||
*
|
*
|
||||||
* @param array $data Raw data to authenticate the user
|
* @param array $data Raw data to authenticate the user
|
||||||
* @param Form $form Optional: If passed, better error messages can be
|
* @param Form $form Optional: If passed, better error messages can be
|
||||||
* produced by using
|
* produced by using
|
||||||
* {@link Form::sessionMessage()}
|
* {@link Form::sessionMessage()}
|
||||||
* @return bool|Member Returns FALSE if authentication fails, otherwise
|
* @return bool|Member Returns FALSE if authentication fails, otherwise
|
||||||
* the member object
|
* the member object
|
||||||
* @see Security::setDefaultAdmin()
|
* @see Security::setDefaultAdmin()
|
||||||
*/
|
*/
|
||||||
public static function authenticate($data, Form $form = null)
|
public static function authenticate($data, Form $form = null)
|
||||||
{
|
{
|
||||||
// Find authenticated member
|
// Find authenticated member
|
||||||
$member = static::authenticate_member($data, $form, $success);
|
$member = static::authenticate_member($data, $form, $success);
|
||||||
|
|
||||||
// Optionally record every login attempt as a {@link LoginAttempt} object
|
// Optionally record every login attempt as a {@link LoginAttempt} object
|
||||||
static::record_login_attempt($data, $member, $success);
|
static::record_login_attempt($data, $member, $success);
|
||||||
|
|
||||||
// Legacy migration to precision-safe password hashes.
|
// Legacy migration to precision-safe password hashes.
|
||||||
// A login-event with cleartext passwords is the only time
|
// A login-event with cleartext passwords is the only time
|
||||||
// when we can rehash passwords to a different hashing algorithm,
|
// when we can rehash passwords to a different hashing algorithm,
|
||||||
// bulk-migration doesn't work due to the nature of hashing.
|
// bulk-migration doesn't work due to the nature of hashing.
|
||||||
// See PasswordEncryptor_LegacyPHPHash class.
|
// See PasswordEncryptor_LegacyPHPHash class.
|
||||||
if($success && $member && isset(self::$migrate_legacy_hashes[$member->PasswordEncryption])) {
|
if ($success && $member && isset(self::$migrate_legacy_hashes[$member->PasswordEncryption])) {
|
||||||
$member->Password = $data['Password'];
|
$member->Password = $data['Password'];
|
||||||
$member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption];
|
$member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption];
|
||||||
$member->write();
|
$member->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
Session::clear('BackURL');
|
Session::clear('BackURL');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $success ? $member : null;
|
return $success ? $member : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that creates the login form for this authentication method
|
* Method that creates the login form for this authentication method
|
||||||
*
|
*
|
||||||
* @param Controller $controller The parent controller, necessary to create the
|
* @param Controller $controller The parent controller, necessary to create the
|
||||||
* appropriate form action tag
|
* appropriate form action tag
|
||||||
* @return Form Returns the login form to use with this authentication
|
* @return Form Returns the login form to use with this authentication
|
||||||
* method
|
* method
|
||||||
*/
|
*/
|
||||||
public static function get_login_form(Controller $controller)
|
public static function get_login_form(Controller $controller)
|
||||||
{
|
{
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
return MemberLoginForm::create($controller, "LoginForm");
|
return MemberLoginForm::create($controller, "LoginForm");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_cms_login_form(Controller $controller)
|
public static function get_cms_login_form(Controller $controller)
|
||||||
{
|
{
|
||||||
/** @skipUpgrade */
|
/** @skipUpgrade */
|
||||||
return CMSMemberLoginForm::create($controller, "LoginForm");
|
return CMSMemberLoginForm::create($controller, "LoginForm");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function supports_cms()
|
public static function supports_cms()
|
||||||
{
|
{
|
||||||
// Don't automatically support subclasses of MemberAuthenticator
|
// Don't automatically support subclasses of MemberAuthenticator
|
||||||
return get_called_class() === __CLASS__;
|
return get_called_class() === __CLASS__;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the authentication method
|
* Get the name of the authentication method
|
||||||
*
|
*
|
||||||
* @return string Returns the name of the authentication method.
|
* @return string Returns the name of the authentication method.
|
||||||
*/
|
*/
|
||||||
public static function get_name()
|
public static function get_name()
|
||||||
{
|
{
|
||||||
return _t('MemberAuthenticator.TITLE', "E-mail & Password");
|
return _t('MemberAuthenticator.TITLE', "E-mail & Password");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ use SilverStripe\Control\Director;
|
|||||||
use SilverStripe\Control\Session;
|
use SilverStripe\Control\Session;
|
||||||
use SilverStripe\Control\Controller;
|
use SilverStripe\Control\Controller;
|
||||||
use SilverStripe\Control\Email\Email;
|
use SilverStripe\Control\Email\Email;
|
||||||
|
use SilverStripe\Dev\Debug;
|
||||||
use SilverStripe\Forms\HiddenField;
|
use SilverStripe\Forms\HiddenField;
|
||||||
use SilverStripe\Forms\FieldList;
|
use SilverStripe\Forms\FieldList;
|
||||||
use SilverStripe\Forms\FormAction;
|
use SilverStripe\Forms\FormAction;
|
||||||
@ -16,6 +17,7 @@ use SilverStripe\Forms\PasswordField;
|
|||||||
use SilverStripe\Forms\CheckboxField;
|
use SilverStripe\Forms\CheckboxField;
|
||||||
use SilverStripe\Forms\LiteralField;
|
use SilverStripe\Forms\LiteralField;
|
||||||
use SilverStripe\Forms\RequiredFields;
|
use SilverStripe\Forms\RequiredFields;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\View\Requirements;
|
use SilverStripe\View\Requirements;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,19 +162,18 @@ JS;
|
|||||||
Requirements::customScript($js, 'MemberLoginFormFieldFocus');
|
Requirements::customScript($js, 'MemberLoginFormFieldFocus');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function restoreFormState()
|
||||||
* Get message from session
|
|
||||||
*/
|
|
||||||
protected function getMessageFromSession()
|
|
||||||
{
|
{
|
||||||
|
parent::restoreFormState();
|
||||||
|
|
||||||
$forceMessage = Session::get('MemberLoginForm.force_message');
|
$forceMessage = Session::get('MemberLoginForm.force_message');
|
||||||
if (($member = Member::currentUser()) && !$forceMessage) {
|
if (($member = Member::currentUser()) && !$forceMessage) {
|
||||||
$this->message = _t(
|
$message = _t(
|
||||||
'Member.LOGGEDINAS',
|
'Member.LOGGEDINAS',
|
||||||
"You're logged in as {name}.",
|
"You're logged in as {name}.",
|
||||||
array('name' => $member->{$this->loggedInAsField})
|
array('name' => $member->{$this->loggedInAsField})
|
||||||
);
|
);
|
||||||
|
$this->setMessage($message, ValidationResult::TYPE_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset forced message
|
// Reset forced message
|
||||||
@ -180,7 +181,7 @@ JS;
|
|||||||
Session::set('MemberLoginForm.force_message', false);
|
Session::set('MemberLoginForm.force_message', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getMessageFromSession();
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -283,11 +284,8 @@ JS;
|
|||||||
$member->logIn();
|
$member->logIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
Session::set(
|
$message = _t('Member.WELCOMEBACK', "Welcome Back, {firstname}", array('firstname' => $firstname));
|
||||||
'Security.Message.message',
|
Security::setLoginMessage($message, ValidationResult::TYPE_GOOD);
|
||||||
_t('Member.WELCOMEBACK', "Welcome Back, {firstname}", array('firstname' => $firstname))
|
|
||||||
);
|
|
||||||
Session::set("Security.Message.type", "good");
|
|
||||||
}
|
}
|
||||||
return Controller::curr()->redirectBack();
|
return Controller::curr()->redirectBack();
|
||||||
}
|
}
|
||||||
|
@ -20,131 +20,131 @@ use SilverStripe\ORM\ValidationResult;
|
|||||||
class PasswordValidator extends Object
|
class PasswordValidator extends Object
|
||||||
{
|
{
|
||||||
|
|
||||||
private static $character_strength_tests = array(
|
private static $character_strength_tests = array(
|
||||||
'lowercase' => '/[a-z]/',
|
'lowercase' => '/[a-z]/',
|
||||||
'uppercase' => '/[A-Z]/',
|
'uppercase' => '/[A-Z]/',
|
||||||
'digits' => '/[0-9]/',
|
'digits' => '/[0-9]/',
|
||||||
'punctuation' => '/[^A-Za-z0-9]/',
|
'punctuation' => '/[^A-Za-z0-9]/',
|
||||||
);
|
);
|
||||||
|
|
||||||
protected $minLength, $minScore, $testNames, $historicalPasswordCount;
|
protected $minLength, $minScore, $testNames, $historicalPasswordCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum password length
|
* Minimum password length
|
||||||
*
|
*
|
||||||
* @param int $minLength
|
* @param int $minLength
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function minLength($minLength)
|
public function minLength($minLength)
|
||||||
{
|
{
|
||||||
$this->minLength = $minLength;
|
$this->minLength = $minLength;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the character strength of the password.
|
* Check the character strength of the password.
|
||||||
*
|
*
|
||||||
* Eg: $this->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"))
|
* Eg: $this->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"))
|
||||||
*
|
*
|
||||||
* @param int $minScore The minimum number of character tests that must pass
|
* @param int $minScore The minimum number of character tests that must pass
|
||||||
* @param array $testNames The names of the tests to perform
|
* @param array $testNames The names of the tests to perform
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function characterStrength($minScore, $testNames)
|
public function characterStrength($minScore, $testNames)
|
||||||
{
|
{
|
||||||
$this->minScore = $minScore;
|
$this->minScore = $minScore;
|
||||||
$this->testNames = $testNames;
|
$this->testNames = $testNames;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check a number of previous passwords that the user has used, and don't let them change to that.
|
* Check a number of previous passwords that the user has used, and don't let them change to that.
|
||||||
*
|
*
|
||||||
* @param int $count
|
* @param int $count
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function checkHistoricalPasswords($count)
|
public function checkHistoricalPasswords($count)
|
||||||
{
|
{
|
||||||
$this->historicalPasswordCount = $count;
|
$this->historicalPasswordCount = $count;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param String $password
|
* @param String $password
|
||||||
* @param Member $member
|
* @param Member $member
|
||||||
* @return ValidationResult
|
* @return ValidationResult
|
||||||
*/
|
*/
|
||||||
public function validate($password, $member)
|
public function validate($password, $member)
|
||||||
{
|
{
|
||||||
$valid = ValidationResult::create();
|
$valid = ValidationResult::create();
|
||||||
|
|
||||||
if($this->minLength) {
|
if ($this->minLength) {
|
||||||
if(strlen($password) < $this->minLength) {
|
if (strlen($password) < $this->minLength) {
|
||||||
$valid->addError(
|
$valid->addError(
|
||||||
sprintf(
|
sprintf(
|
||||||
_t(
|
_t(
|
||||||
'PasswordValidator.TOOSHORT',
|
'PasswordValidator.TOOSHORT',
|
||||||
'Password is too short, it must be %s or more characters long'
|
'Password is too short, it must be %s or more characters long'
|
||||||
),
|
),
|
||||||
$this->minLength
|
$this->minLength
|
||||||
),
|
),
|
||||||
'bad',
|
'bad',
|
||||||
'TOO_SHORT'
|
'TOO_SHORT'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->minScore) {
|
if ($this->minScore) {
|
||||||
$score = 0;
|
$score = 0;
|
||||||
$missedTests = array();
|
$missedTests = array();
|
||||||
foreach($this->testNames as $name) {
|
foreach ($this->testNames as $name) {
|
||||||
if(preg_match(self::config()->character_strength_tests[$name], $password)) {
|
if (preg_match(self::config()->character_strength_tests[$name], $password)) {
|
||||||
$score++;
|
$score++;
|
||||||
} else {
|
} else {
|
||||||
$missedTests[] = _t(
|
$missedTests[] = _t(
|
||||||
'PasswordValidator.STRENGTHTEST' . strtoupper($name),
|
'PasswordValidator.STRENGTHTEST' . strtoupper($name),
|
||||||
$name,
|
$name,
|
||||||
'The user needs to add this to their password for more complexity'
|
'The user needs to add this to their password for more complexity'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($score < $this->minScore) {
|
if ($score < $this->minScore) {
|
||||||
$valid->addError(
|
$valid->addError(
|
||||||
sprintf(
|
sprintf(
|
||||||
_t(
|
_t(
|
||||||
'PasswordValidator.LOWCHARSTRENGTH',
|
'PasswordValidator.LOWCHARSTRENGTH',
|
||||||
'Please increase password strength by adding some of the following characters: %s'
|
'Please increase password strength by adding some of the following characters: %s'
|
||||||
),
|
),
|
||||||
implode(', ', $missedTests)
|
implode(', ', $missedTests)
|
||||||
),
|
),
|
||||||
'bad',
|
'bad',
|
||||||
'LOW_CHARACTER_STRENGTH'
|
'LOW_CHARACTER_STRENGTH'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->historicalPasswordCount) {
|
if ($this->historicalPasswordCount) {
|
||||||
$previousPasswords = MemberPassword::get()
|
$previousPasswords = MemberPassword::get()
|
||||||
->where(array('"MemberPassword"."MemberID"' => $member->ID))
|
->where(array('"MemberPassword"."MemberID"' => $member->ID))
|
||||||
->sort('"Created" DESC, "ID" DESC')
|
->sort('"Created" DESC, "ID" DESC')
|
||||||
->limit($this->historicalPasswordCount);
|
->limit($this->historicalPasswordCount);
|
||||||
/** @var MemberPassword $previousPassword */
|
/** @var MemberPassword $previousPassword */
|
||||||
foreach($previousPasswords as $previousPassword) {
|
foreach ($previousPasswords as $previousPassword) {
|
||||||
if($previousPassword->checkPassword($password)) {
|
if ($previousPassword->checkPassword($password)) {
|
||||||
$valid->addError(
|
$valid->addError(
|
||||||
_t(
|
_t(
|
||||||
'PasswordValidator.PREVPASSWORD',
|
'PasswordValidator.PREVPASSWORD',
|
||||||
'You\'ve already used that password in the past, please choose a new password'
|
'You\'ve already used that password in the past, please choose a new password'
|
||||||
),
|
),
|
||||||
'bad',
|
'bad',
|
||||||
'PREVIOUS_PASSWORD'
|
'PREVIOUS_PASSWORD'
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $valid;
|
return $valid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,50 +13,50 @@ use SilverStripe\ORM\DataObject;
|
|||||||
*/
|
*/
|
||||||
class PermissionRoleCode extends DataObject
|
class PermissionRoleCode extends DataObject
|
||||||
{
|
{
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
"Code" => "Varchar",
|
"Code" => "Varchar",
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $has_one = array(
|
private static $has_one = array(
|
||||||
"Role" => "SilverStripe\\Security\\PermissionRole",
|
"Role" => "SilverStripe\\Security\\PermissionRole",
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $table_name = "PermissionRoleCode";
|
private static $table_name = "PermissionRoleCode";
|
||||||
|
|
||||||
public function validate()
|
public function validate()
|
||||||
{
|
{
|
||||||
$result = parent::validate();
|
$result = parent::validate();
|
||||||
|
|
||||||
// Check that new code doesn't increase privileges, unless an admin is editing.
|
// Check that new code doesn't increase privileges, unless an admin is editing.
|
||||||
$privilegedCodes = Permission::config()->privileged_permissions;
|
$privilegedCodes = Permission::config()->privileged_permissions;
|
||||||
if ($this->Code
|
if ($this->Code
|
||||||
&& in_array($this->Code, $privilegedCodes)
|
&& in_array($this->Code, $privilegedCodes)
|
||||||
&& !Permission::check('ADMIN')
|
&& !Permission::check('ADMIN')
|
||||||
) {
|
) {
|
||||||
$result->addError(sprintf(
|
$result->addError(sprintf(
|
||||||
_t(
|
_t(
|
||||||
'PermissionRoleCode.PermsError',
|
'PermissionRoleCode.PermsError',
|
||||||
'Can\'t assign code "%s" with privileged permissions (requires ADMIN access)'
|
'Can\'t assign code "%s" with privileged permissions (requires ADMIN access)'
|
||||||
),
|
),
|
||||||
$this->Code
|
$this->Code
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canCreate($member = null, $context = array())
|
public function canCreate($member = null, $context = array())
|
||||||
{
|
{
|
||||||
return Permission::check('APPLY_ROLES', 'any', $member);
|
return Permission::check('APPLY_ROLES', 'any', $member);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canEdit($member = null)
|
public function canEdit($member = null)
|
||||||
{
|
{
|
||||||
return Permission::check('APPLY_ROLES', 'any', $member);
|
return Permission::check('APPLY_ROLES', 'any', $member);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canDelete($member = null)
|
public function canDelete($member = null)
|
||||||
{
|
{
|
||||||
return Permission::check('APPLY_ROLES', 'any', $member);
|
return Permission::check('APPLY_ROLES', 'any', $member);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ use SilverStripe\ORM\ArrayList;
|
|||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\DB;
|
use SilverStripe\ORM\DB;
|
||||||
use SilverStripe\ORM\FieldType\DBField;
|
use SilverStripe\ORM\FieldType\DBField;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\View\ArrayData;
|
use SilverStripe\View\ArrayData;
|
||||||
use SilverStripe\View\SSViewer;
|
use SilverStripe\View\SSViewer;
|
||||||
use SilverStripe\View\TemplateGlobalProvider;
|
use SilverStripe\View\TemplateGlobalProvider;
|
||||||
@ -324,7 +325,7 @@ class Security extends Controller implements TemplateGlobalProvider
|
|||||||
// Somewhat hackish way to render a login form with an error message.
|
// Somewhat hackish way to render a login form with an error message.
|
||||||
$me = new Security();
|
$me = new Security();
|
||||||
$form = $me->LoginForm();
|
$form = $me->LoginForm();
|
||||||
$form->sessionMessage($message, 'warning');
|
$form->sessionMessage($message, ValidationResult::TYPE_WARNING);
|
||||||
Session::set('MemberLoginForm.force_message', 1);
|
Session::set('MemberLoginForm.force_message', 1);
|
||||||
$loginResponse = $me->login();
|
$loginResponse = $me->login();
|
||||||
if ($loginResponse instanceof HTTPResponse) {
|
if ($loginResponse instanceof HTTPResponse) {
|
||||||
@ -340,8 +341,7 @@ class Security extends Controller implements TemplateGlobalProvider
|
|||||||
$message = $messageSet['default'];
|
$message = $messageSet['default'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Session::set("Security.Message.message", $message);
|
static::setLoginMessage($message, ValidationResult::TYPE_WARNING);
|
||||||
Session::set("Security.Message.type", 'warning');
|
|
||||||
|
|
||||||
Session::set("BackURL", $_SERVER['REQUEST_URI']);
|
Session::set("BackURL", $_SERVER['REQUEST_URI']);
|
||||||
|
|
||||||
@ -349,10 +349,10 @@ class Security extends Controller implements TemplateGlobalProvider
|
|||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$controller->extend('permissionDenied', $member);
|
$controller->extend('permissionDenied', $member);
|
||||||
|
|
||||||
return $controller->redirect(
|
return $controller->redirect(Controller::join_links(
|
||||||
Config::inst()->get('SilverStripe\\Security\\Security', 'login_url')
|
static::config()->get('login_url'),
|
||||||
. "?BackURL=" . urlencode($_SERVER['REQUEST_URI'])
|
"?BackURL=" . urlencode($_SERVER['REQUEST_URI'])
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function init()
|
protected function init()
|
||||||
@ -559,11 +559,36 @@ class Security extends Controller implements TemplateGlobalProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
$messageType = Session::get('Security.Message.type');
|
$messageType = Session::get('Security.Message.type');
|
||||||
if ($messageType === 'bad') {
|
$messageCast = Session::get('Security.Message.cast');
|
||||||
return "<p class=\"message $messageType\">$message</p>";
|
if ($messageCast !== ValidationResult::CAST_HTML) {
|
||||||
} else {
|
$message = Convert::raw2xml($message);
|
||||||
return "<p>$message</p>";
|
|
||||||
}
|
}
|
||||||
|
return sprintf('<p class="message %s">%s</p>', Convert::raw2att($messageType), $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the next message to display for the security login page. Defaults to warning
|
||||||
|
*
|
||||||
|
* @param string $message Message
|
||||||
|
* @param string $messageType Message type. One of ValidationResult::TYPE_*
|
||||||
|
* @param string $messageCast Message cast. One of ValidationResult::CAST_*
|
||||||
|
*/
|
||||||
|
public static function setLoginMessage(
|
||||||
|
$message,
|
||||||
|
$messageType = ValidationResult::TYPE_WARNING,
|
||||||
|
$messageCast = ValidationResult::CAST_TEXT
|
||||||
|
) {
|
||||||
|
Session::set("Security.Message.message", $message);
|
||||||
|
Session::set("Security.Message.type", $messageType);
|
||||||
|
Session::set("Security.Message.cast", $messageCast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear login message
|
||||||
|
*/
|
||||||
|
public static function clearLoginMessage()
|
||||||
|
{
|
||||||
|
Session::clear("Security.Message");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -603,7 +628,7 @@ class Security extends Controller implements TemplateGlobalProvider
|
|||||||
$message = $this->getLoginMessage($messageType);
|
$message = $this->getLoginMessage($messageType);
|
||||||
|
|
||||||
// We've displayed the message in the form output, so reset it for the next run.
|
// We've displayed the message in the form output, so reset it for the next run.
|
||||||
Session::clear('Security.Message');
|
static::clearLoginMessage();
|
||||||
|
|
||||||
// only display tabs when more than one authenticator is provided
|
// only display tabs when more than one authenticator is provided
|
||||||
// to save bandwidth and reduce the amount of custom styling needed
|
// to save bandwidth and reduce the amount of custom styling needed
|
||||||
|
@ -6,7 +6,7 @@ Feature: Log in
|
|||||||
|
|
||||||
Scenario: Bad login
|
Scenario: Bad login
|
||||||
Given I log in with "bad@example.com" and "badpassword"
|
Given I log in with "bad@example.com" and "badpassword"
|
||||||
Then I will see a "bad" log-in message
|
Then I will see a "error" log-in message
|
||||||
|
|
||||||
Scenario: Valid login
|
Scenario: Valid login
|
||||||
Given I am logged in with "ADMIN" permissions
|
Given I am logged in with "ADMIN" permissions
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Assets\Tests;
|
namespace SilverStripe\Assets\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Assets\Image;
|
||||||
use SilverStripe\Assets\Storage\AssetStore;
|
use SilverStripe\Assets\Storage\AssetStore;
|
||||||
use SilverStripe\Assets\Tests\FileTest\MyCustomFile;
|
use SilverStripe\Assets\Tests\FileTest\MyCustomFile;
|
||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationException;
|
||||||
@ -178,22 +179,21 @@ class FileTest extends SapphireTest {
|
|||||||
|
|
||||||
// Invalid ext
|
// Invalid ext
|
||||||
$file->Name = 'asdf.php';
|
$file->Name = 'asdf.php';
|
||||||
$v = $file->validate();
|
$result = $file->validate();
|
||||||
$this->assertFalse($v->valid());
|
$this->assertFalse($result->isValid());
|
||||||
$this->assertContains('Extension is not allowed', $v->message());
|
$messages = $result->getMessages();
|
||||||
|
$this->assertEquals(1, count($messages));
|
||||||
|
$this->assertEquals('Extension is not allowed', $messages[0]['message']);
|
||||||
|
|
||||||
// Valid ext
|
// Valid ext
|
||||||
$file->Name = 'asdf.txt';
|
$file->Name = 'asdf.txt';
|
||||||
$v = $file->validate();
|
$result = $file->validate();
|
||||||
$this->assertTrue($v->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
// Capital extension is valid as well
|
// Capital extension is valid as well
|
||||||
$file->Name = 'asdf.TXT';
|
$file->Name = 'asdf.TXT';
|
||||||
$v = $file->validate();
|
$result = $file->validate();
|
||||||
$this->assertTrue($v->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
Config::inst()->remove(File::class, 'allowed_extensions');
|
|
||||||
Config::inst()->update(File::class, 'allowed_extensions', $orig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAppCategory() {
|
public function testAppCategory() {
|
||||||
@ -372,7 +372,7 @@ class FileTest extends SapphireTest {
|
|||||||
|
|
||||||
public function testNameAndTitleGeneration() {
|
public function testNameAndTitleGeneration() {
|
||||||
// When name is assigned, title is automatically assigned
|
// When name is assigned, title is automatically assigned
|
||||||
$file = $this->objFromFixture('SilverStripe\\Assets\\Image', 'setfromname');
|
$file = $this->objFromFixture(Image::class, 'setfromname');
|
||||||
$this->assertEquals('FileTest', $file->Title);
|
$this->assertEquals('FileTest', $file->Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,13 +386,13 @@ class FileTest extends SapphireTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testFileType() {
|
public function testFileType() {
|
||||||
$file = $this->objFromFixture('SilverStripe\\Assets\\Image', 'gif');
|
$file = $this->objFromFixture(Image::class, 'gif');
|
||||||
$this->assertEquals("GIF image - good for diagrams", $file->FileType);
|
$this->assertEquals("GIF image - good for diagrams", $file->FileType);
|
||||||
|
|
||||||
$file = $this->objFromFixture(File::class, 'pdf');
|
$file = $this->objFromFixture(File::class, 'pdf');
|
||||||
$this->assertEquals("Adobe Acrobat PDF file", $file->FileType);
|
$this->assertEquals("Adobe Acrobat PDF file", $file->FileType);
|
||||||
|
|
||||||
$file = $this->objFromFixture('SilverStripe\\Assets\\Image', 'gifupper');
|
$file = $this->objFromFixture(Image::class, 'gifupper');
|
||||||
$this->assertEquals("GIF image - good for diagrams", $file->FileType);
|
$this->assertEquals("GIF image - good for diagrams", $file->FileType);
|
||||||
|
|
||||||
/* Only a few file types are given special descriptions; the rest are unknown */
|
/* Only a few file types are given special descriptions; the rest are unknown */
|
||||||
@ -450,7 +450,7 @@ class FileTest extends SapphireTest {
|
|||||||
$newTitle = "FileTest-folder-renamed";
|
$newTitle = "FileTest-folder-renamed";
|
||||||
|
|
||||||
//rename a folder's title
|
//rename a folder's title
|
||||||
$folderID = $this->objFromFixture("SilverStripe\\Assets\\Folder","folder2")->ID;
|
$folderID = $this->objFromFixture(Folder::class,"folder2")->ID;
|
||||||
$folder = DataObject::get_by_id(Folder::class,$folderID);
|
$folder = DataObject::get_by_id(Folder::class,$folderID);
|
||||||
$folder->Title = $newTitle;
|
$folder->Title = $newTitle;
|
||||||
$folder->write();
|
$folder->write();
|
||||||
@ -508,30 +508,30 @@ class FileTest extends SapphireTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCanEdit() {
|
public function testCanEdit() {
|
||||||
$file = $this->objFromFixture('SilverStripe\\Assets\\Image', 'gif');
|
$file = $this->objFromFixture(Image::class, 'gif');
|
||||||
|
|
||||||
// Test anonymous permissions
|
// Test anonymous permissions
|
||||||
Session::set('loggedInAs', null);
|
Session::set('loggedInAs', null);
|
||||||
$this->assertFalse($file->canEdit(), "Anonymous users can't edit files");
|
$this->assertFalse($file->canEdit(), "Anonymous users can't edit files");
|
||||||
|
|
||||||
// Test permissionless user
|
// Test permissionless user
|
||||||
$this->objFromFixture('SilverStripe\\Security\\Member', 'frontend')->logIn();
|
$this->objFromFixture(Member::class, 'frontend')->logIn();
|
||||||
$this->assertFalse($file->canEdit(), "Permissionless users can't edit files");
|
$this->assertFalse($file->canEdit(), "Permissionless users can't edit files");
|
||||||
|
|
||||||
// Test global CMS section users
|
// Test global CMS section users
|
||||||
$this->objFromFixture('SilverStripe\\Security\\Member', 'cms')->logIn();
|
$this->objFromFixture(Member::class, 'cms')->logIn();
|
||||||
$this->assertTrue($file->canEdit(), "Users with all CMS section access can edit files");
|
$this->assertTrue($file->canEdit(), "Users with all CMS section access can edit files");
|
||||||
|
|
||||||
// Test cms access users without file access
|
// Test cms access users without file access
|
||||||
$this->objFromFixture('SilverStripe\\Security\\Member', 'security')->logIn();
|
$this->objFromFixture(Member::class, 'security')->logIn();
|
||||||
$this->assertFalse($file->canEdit(), "Security CMS users can't edit files");
|
$this->assertFalse($file->canEdit(), "Security CMS users can't edit files");
|
||||||
|
|
||||||
// Test asset-admin user
|
// Test asset-admin user
|
||||||
$this->objFromFixture('SilverStripe\\Security\\Member', 'assetadmin')->logIn();
|
$this->objFromFixture(Member::class, 'assetadmin')->logIn();
|
||||||
$this->assertTrue($file->canEdit(), "Asset admin users can edit files");
|
$this->assertTrue($file->canEdit(), "Asset admin users can edit files");
|
||||||
|
|
||||||
// Test admin
|
// Test admin
|
||||||
$this->objFromFixture('SilverStripe\\Security\\Member', 'admin')->logIn();
|
$this->objFromFixture(Member::class, 'admin')->logIn();
|
||||||
$this->assertTrue($file->canEdit(), "Admins can edit files");
|
$this->assertTrue($file->canEdit(), "Admins can edit files");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,10 +35,7 @@ class Validator extends Upload_Validator implements TestOnly
|
|||||||
|
|
||||||
// extension validation
|
// extension validation
|
||||||
if(!$this->isValidExtension()) {
|
if(!$this->isValidExtension()) {
|
||||||
$this->errors[] = _t(
|
$this->errors[] = _t('File.INVALIDEXTENSIONSHORT', 'Extension is not allowed');
|
||||||
'File.INVALIDEXTENSION_SHORT',
|
|
||||||
'Extension is not allowed'
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +346,7 @@ class AssetFieldTest extends FunctionalTest {
|
|||||||
|
|
||||||
$form = new TestForm();
|
$form = new TestForm();
|
||||||
$form->loadDataFrom($data, true);
|
$form->loadDataFrom($data, true);
|
||||||
if($form->validate()) {
|
if($form->validationResult()->isValid()) {
|
||||||
$record = $form->getRecord();
|
$record = $form->getRecord();
|
||||||
$form->saveInto($record);
|
$form->saveInto($record);
|
||||||
$record->write();
|
$record->write();
|
||||||
|
@ -4,11 +4,13 @@ namespace SilverStripe\Forms\Tests\EmailFieldTest;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use SilverStripe\Forms\Validator;
|
use SilverStripe\Forms\Validator;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
|
||||||
class TestValidator extends Validator
|
class TestValidator extends Validator
|
||||||
{
|
{
|
||||||
public function validationError($fieldName, $message, $messageType = '')
|
public function validationError(
|
||||||
{
|
$fieldName, $message, $messageType = ValidationResult::TYPE_ERROR, $cast = ValidationResult::CAST_TEXT
|
||||||
|
) {
|
||||||
throw new Exception($message);
|
throw new Exception($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,11 +18,11 @@ class FieldGroupTest extends SapphireTest {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$textField->setError('Test error message', 'warning');
|
$textField->setMessage('Test error message', 'error');
|
||||||
$emailField->setError('Test error message', 'error');
|
$emailField->setMessage('Test error warning', 'warning');
|
||||||
|
|
||||||
$this->assertEquals('Test error message, Test error message.', $fieldGroup->Message());
|
$this->assertEquals('Test error message, Test error warning.', $fieldGroup->getMessage());
|
||||||
$this->assertEquals('warning. error', $fieldGroup->MessageType());
|
$this->assertEquals('error', $fieldGroup->getMessageType());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,7 @@ class FileFieldTest extends FunctionalTest {
|
|||||||
);
|
);
|
||||||
$fileField->setValue($fileFieldValue);
|
$fileField->setValue($fileFieldValue);
|
||||||
|
|
||||||
$this->assertTrue(
|
$this->assertTrue($form->validationResult()->isValid());
|
||||||
$form->validate()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +61,7 @@ class FileFieldTest extends FunctionalTest {
|
|||||||
$fileField->setValue($fileFieldValue);
|
$fileField->setValue($fileFieldValue);
|
||||||
|
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$form->validate(),
|
$form->validationResult()->isValid(),
|
||||||
'An error occured when uploading a file, but the validator returned true'
|
'An error occured when uploading a file, but the validator returned true'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -72,7 +70,7 @@ class FileFieldTest extends FunctionalTest {
|
|||||||
$fileField->setValue($fileFieldValue);
|
$fileField->setValue($fileFieldValue);
|
||||||
|
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$form->validate(),
|
$form->validationResult()->isValid(),
|
||||||
'An empty array was passed as parameter for an uploaded file, but the validator returned true'
|
'An empty array was passed as parameter for an uploaded file, but the validator returned true'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -81,7 +79,7 @@ class FileFieldTest extends FunctionalTest {
|
|||||||
$fileField->setValue($fileFieldValue);
|
$fileField->setValue($fileFieldValue);
|
||||||
|
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$form->validate(),
|
$form->validationResult()->isValid(),
|
||||||
'A null value was passed as parameter for an uploaded file, but the validator returned true'
|
'A null value was passed as parameter for an uploaded file, but the validator returned true'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -332,11 +332,10 @@ class FormFieldTest extends SapphireTest {
|
|||||||
$field = new FormField('MyField', 'My Field');
|
$field = new FormField('MyField', 'My Field');
|
||||||
$validator = new RequiredFields('MyField');
|
$validator = new RequiredFields('MyField');
|
||||||
$form = new Form(new Controller(), 'TestForm', new FieldList($field), new FieldList(), $validator);
|
$form = new Form(new Controller(), 'TestForm', new FieldList($field), new FieldList(), $validator);
|
||||||
$form->validate();
|
$form->validationResult();
|
||||||
$form->setupFormErrors();
|
|
||||||
$schema = $field->getSchemaState();
|
$schema = $field->getSchemaState();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
['html' => '"My Field" is required'],
|
'"My Field" is required',
|
||||||
$schema['message']['value']
|
$schema['message']['value']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,6 @@ class FormSchemaTest extends SapphireTest {
|
|||||||
'name' => 'SecurityID',
|
'name' => 'SecurityID',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'valid' => null,
|
|
||||||
'messages' => [],
|
'messages' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -104,10 +103,9 @@ class FormSchemaTest extends SapphireTest {
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'messages' => [[
|
'messages' => [[
|
||||||
'value' => ['html' => 'All saved'],
|
'value' => 'All saved',
|
||||||
'type' => 'good'
|
'type' => 'good'
|
||||||
]],
|
]],
|
||||||
'valid' => null,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$state = $formSchema->getState($form);
|
$state = $formSchema->getState($form);
|
||||||
@ -123,7 +121,7 @@ class FormSchemaTest extends SapphireTest {
|
|||||||
$form->loadDataFrom([
|
$form->loadDataFrom([
|
||||||
'Title' => null,
|
'Title' => null,
|
||||||
]);
|
]);
|
||||||
$this->assertFalse($form->validate());
|
$this->assertFalse($form->validationResult()->isValid());
|
||||||
$formSchema = new FormSchema();
|
$formSchema = new FormSchema();
|
||||||
$expected = [
|
$expected = [
|
||||||
'id' => 'Form_TestForm',
|
'id' => 'Form_TestForm',
|
||||||
@ -132,7 +130,7 @@ class FormSchemaTest extends SapphireTest {
|
|||||||
'id' => 'Form_TestForm_Title',
|
'id' => 'Form_TestForm_Title',
|
||||||
'value' => null,
|
'value' => null,
|
||||||
'message' => [
|
'message' => [
|
||||||
'value' => ['html' => '"Title" is required'],
|
'value' => '"Title" is required',
|
||||||
'type' => 'required'
|
'type' => 'required'
|
||||||
],
|
],
|
||||||
'data' => [],
|
'data' => [],
|
||||||
@ -146,7 +144,6 @@ class FormSchemaTest extends SapphireTest {
|
|||||||
'name' => 'SecurityID',
|
'name' => 'SecurityID',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'valid' => false,
|
|
||||||
'messages' => []
|
'messages' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -165,7 +162,7 @@ class FormSchemaTest extends SapphireTest {
|
|||||||
->setIcon('save'),
|
->setIcon('save'),
|
||||||
(new FormAction("cancel", "Cancel"))
|
(new FormAction("cancel", "Cancel"))
|
||||||
->setUseButtonTag(true),
|
->setUseButtonTag(true),
|
||||||
new PopoverField("More options", [
|
$pop = new PopoverField("More options", [
|
||||||
new FormAction("publish", "Publish record"),
|
new FormAction("publish", "Publish record"),
|
||||||
new FormAction("archive", "Archive"),
|
new FormAction("archive", "Archive"),
|
||||||
])
|
])
|
||||||
|
@ -7,7 +7,8 @@ use SilverStripe\Forms\Tests\FormTest\ControllerWithSecurityToken;
|
|||||||
use SilverStripe\Forms\Tests\FormTest\ControllerWithStrictPostCheck;
|
use SilverStripe\Forms\Tests\FormTest\ControllerWithStrictPostCheck;
|
||||||
use SilverStripe\Forms\Tests\FormTest\Player;
|
use SilverStripe\Forms\Tests\FormTest\Player;
|
||||||
use SilverStripe\Forms\Tests\FormTest\Team;
|
use SilverStripe\Forms\Tests\FormTest\Team;
|
||||||
use SilverStripe\ORM\DataModel;
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
use SilverStripe\Security\NullSecurityToken;
|
||||||
use SilverStripe\Security\SecurityToken;
|
use SilverStripe\Security\SecurityToken;
|
||||||
use SilverStripe\Security\RandomGenerator;
|
use SilverStripe\Security\RandomGenerator;
|
||||||
use SilverStripe\Dev\CSSContentParser;
|
use SilverStripe\Dev\CSSContentParser;
|
||||||
@ -255,7 +256,7 @@ class FormTest extends FunctionalTest {
|
|||||||
$form->saveInto($object);
|
$form->saveInto($object);
|
||||||
$playersIds = $object->Players()->getIDList();
|
$playersIds = $object->Players()->getIDList();
|
||||||
|
|
||||||
$this->assertTrue($form->validate());
|
$this->assertTrue($form->validationResult()->isValid());
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$playersIds,
|
$playersIds,
|
||||||
array(),
|
array(),
|
||||||
@ -420,7 +421,7 @@ class FormTest extends FunctionalTest {
|
|||||||
public function testSessionSuccessMessage() {
|
public function testSessionSuccessMessage() {
|
||||||
$this->get('FormTest_Controller');
|
$this->get('FormTest_Controller');
|
||||||
|
|
||||||
$response = $this->post(
|
$this->post(
|
||||||
'FormTest_Controller/Form',
|
'FormTest_Controller/Form',
|
||||||
array(
|
array(
|
||||||
'Email' => 'test@test.com',
|
'Email' => 'test@test.com',
|
||||||
@ -439,12 +440,12 @@ class FormTest extends FunctionalTest {
|
|||||||
public function testValidationException() {
|
public function testValidationException() {
|
||||||
$this->get('FormTest_Controller');
|
$this->get('FormTest_Controller');
|
||||||
|
|
||||||
$response = $this->post(
|
$this->post(
|
||||||
'FormTest_Controller/Form',
|
'FormTest_Controller/Form',
|
||||||
array(
|
array(
|
||||||
'Email' => 'test@test.com',
|
'Email' => 'test@test.com',
|
||||||
'SomeRequiredField' => 'test',
|
'SomeRequiredField' => 'test',
|
||||||
'action_triggerException' => 1,
|
'action_doTriggerException' => 1,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$this->assertPartialMatchBySelector(
|
$this->assertPartialMatchBySelector(
|
||||||
@ -468,12 +469,12 @@ class FormTest extends FunctionalTest {
|
|||||||
SecurityToken::enable();
|
SecurityToken::enable();
|
||||||
|
|
||||||
$form1 = $this->getStubForm();
|
$form1 = $this->getStubForm();
|
||||||
$this->assertInstanceOf('SilverStripe\\Security\\SecurityToken', $form1->getSecurityToken());
|
$this->assertInstanceOf(SecurityToken::class, $form1->getSecurityToken());
|
||||||
|
|
||||||
SecurityToken::disable();
|
SecurityToken::disable();
|
||||||
|
|
||||||
$form2 = $this->getStubForm();
|
$form2 = $this->getStubForm();
|
||||||
$this->assertInstanceOf('SilverStripe\\Security\\NullSecurityToken', $form2->getSecurityToken());
|
$this->assertInstanceOf(NullSecurityToken::class, $form2->getSecurityToken());
|
||||||
|
|
||||||
SecurityToken::enable();
|
SecurityToken::enable();
|
||||||
}
|
}
|
||||||
@ -500,7 +501,7 @@ class FormTest extends FunctionalTest {
|
|||||||
SecurityToken::enable();
|
SecurityToken::enable();
|
||||||
$expectedToken = SecurityToken::inst()->getValue();
|
$expectedToken = SecurityToken::inst()->getValue();
|
||||||
|
|
||||||
$response = $this->get('FormTest_ControllerWithSecurityToken');
|
$this->get('FormTest_ControllerWithSecurityToken');
|
||||||
// can't use submitForm() as it'll automatically insert SecurityID into the POST data
|
// can't use submitForm() as it'll automatically insert SecurityID into the POST data
|
||||||
$response = $this->post(
|
$response = $this->post(
|
||||||
'FormTest_ControllerWithSecurityToken/Form',
|
'FormTest_ControllerWithSecurityToken/Form',
|
||||||
@ -518,7 +519,7 @@ class FormTest extends FunctionalTest {
|
|||||||
$this->assertNotEquals($invalidToken, $expectedToken);
|
$this->assertNotEquals($invalidToken, $expectedToken);
|
||||||
|
|
||||||
// Test token with request
|
// Test token with request
|
||||||
$response = $this->get('FormTest_ControllerWithSecurityToken');
|
$this->get('FormTest_ControllerWithSecurityToken');
|
||||||
$response = $this->post(
|
$response = $this->post(
|
||||||
'FormTest_ControllerWithSecurityToken/Form',
|
'FormTest_ControllerWithSecurityToken/Form',
|
||||||
array(
|
array(
|
||||||
@ -541,7 +542,7 @@ class FormTest extends FunctionalTest {
|
|||||||
$attrs = $matched[0]->attributes();
|
$attrs = $matched[0]->attributes();
|
||||||
$this->assertEquals('test@test.com', (string)$attrs['value'], 'Submitted data is preserved');
|
$this->assertEquals('test@test.com', (string)$attrs['value'], 'Submitted data is preserved');
|
||||||
|
|
||||||
$response = $this->get('FormTest_ControllerWithSecurityToken');
|
$this->get('FormTest_ControllerWithSecurityToken');
|
||||||
$tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
|
$tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
1,
|
1,
|
||||||
@ -561,13 +562,13 @@ class FormTest extends FunctionalTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testStrictFormMethodChecking() {
|
public function testStrictFormMethodChecking() {
|
||||||
$response = $this->get('FormTest_ControllerWithStrictPostCheck');
|
$this->get('FormTest_ControllerWithStrictPostCheck');
|
||||||
$response = $this->get(
|
$response = $this->get(
|
||||||
'FormTest_ControllerWithStrictPostCheck/Form/?Email=test@test.com&action_doSubmit=1'
|
'FormTest_ControllerWithStrictPostCheck/Form/?Email=test@test.com&action_doSubmit=1'
|
||||||
);
|
);
|
||||||
$this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
|
$this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
|
||||||
|
|
||||||
$response = $this->get('FormTest_ControllerWithStrictPostCheck');
|
$this->get('FormTest_ControllerWithStrictPostCheck');
|
||||||
$response = $this->post(
|
$response = $this->post(
|
||||||
'FormTest_ControllerWithStrictPostCheck/Form',
|
'FormTest_ControllerWithStrictPostCheck/Form',
|
||||||
array(
|
array(
|
||||||
@ -784,9 +785,7 @@ class FormTest extends FunctionalTest {
|
|||||||
|
|
||||||
function testMessageEscapeHtml() {
|
function testMessageEscapeHtml() {
|
||||||
$form = $this->getStubForm();
|
$form = $this->getStubForm();
|
||||||
$form->getController()->handleRequest(new HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
|
$form->setMessage('<em>Escaped HTML</em>', 'good', ValidationResult::CAST_TEXT);
|
||||||
$form->sessionMessage('<em>Escaped HTML</em>', 'good', true);
|
|
||||||
$form->setupFormErrors();
|
|
||||||
$parser = new CSSContentParser($form->forTemplate());
|
$parser = new CSSContentParser($form->forTemplate());
|
||||||
$messageEls = $parser->getBySelector('.message');
|
$messageEls = $parser->getBySelector('.message');
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
@ -795,9 +794,7 @@ class FormTest extends FunctionalTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$form = $this->getStubForm();
|
$form = $this->getStubForm();
|
||||||
$form->getController()->handleRequest(new HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
|
$form->setMessage('<em>Unescaped HTML</em>', 'good', ValidationResult::CAST_HTML);
|
||||||
$form->sessionMessage('<em>Unescaped HTML</em>', 'good', false);
|
|
||||||
$form->setupFormErrors();
|
|
||||||
$parser = new CSSContentParser($form->forTemplate());
|
$parser = new CSSContentParser($form->forTemplate());
|
||||||
$messageEls = $parser->getBySelector('.message');
|
$messageEls = $parser->getBySelector('.message');
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
@ -806,11 +803,9 @@ class FormTest extends FunctionalTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testFieldMessageEscapeHtml() {
|
public function testFieldMessageEscapeHtml() {
|
||||||
$form = $this->getStubForm();
|
$form = $this->getStubForm();
|
||||||
$form->getController()->handleRequest(new HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
|
$form->Fields()->dataFieldByName('key1')->setMessage('<em>Escaped HTML</em>', 'good');
|
||||||
$form->getSessionValidationResult()->addFieldMessage('key1', '<em>Escaped HTML</em>', 'good');
|
|
||||||
$form->setupFormErrors();
|
|
||||||
$parser = new CSSContentParser($result = $form->forTemplate());
|
$parser = new CSSContentParser($result = $form->forTemplate());
|
||||||
$messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
|
$messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
@ -818,10 +813,12 @@ class FormTest extends FunctionalTest {
|
|||||||
$messageEls[0]->asXML()
|
$messageEls[0]->asXML()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Test with HTML
|
||||||
$form = $this->getStubForm();
|
$form = $this->getStubForm();
|
||||||
$form->getController()->handleRequest(new HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
|
$form
|
||||||
$form->getSessionValidationResult()->addFieldMessage('key1', '<em>Unescaped HTML</em>', 'good', null, false);
|
->Fields()
|
||||||
$form->setupFormErrors();
|
->dataFieldByName('key1')
|
||||||
|
->setMessage('<em>Unescaped HTML</em>', 'good', ValidationResult::CAST_HTML);
|
||||||
$parser = new CSSContentParser($form->forTemplate());
|
$parser = new CSSContentParser($form->forTemplate());
|
||||||
$messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
|
$messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
|
@ -4,9 +4,12 @@ namespace SilverStripe\Forms\Tests\FormTest;
|
|||||||
|
|
||||||
use SilverStripe\Dev\TestOnly;
|
use SilverStripe\Dev\TestOnly;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\ManyManyList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @skipUpgrade
|
* @skipUpgrade
|
||||||
|
*
|
||||||
|
* @method ManyManyList Players()
|
||||||
*/
|
*/
|
||||||
class Team extends DataObject implements TestOnly
|
class Team extends DataObject implements TestOnly
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,8 @@ use SilverStripe\Forms\FormAction;
|
|||||||
use SilverStripe\Forms\NumericField;
|
use SilverStripe\Forms\NumericField;
|
||||||
use SilverStripe\Forms\RequiredFields;
|
use SilverStripe\Forms\RequiredFields;
|
||||||
use SilverStripe\Forms\TextField;
|
use SilverStripe\Forms\TextField;
|
||||||
|
use SilverStripe\ORM\ValidationException;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\View\SSViewer;
|
use SilverStripe\View\SSViewer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,6 +56,7 @@ class TestController extends Controller implements TestOnly
|
|||||||
),
|
),
|
||||||
new FieldList(
|
new FieldList(
|
||||||
FormAction::create('doSubmit'),
|
FormAction::create('doSubmit'),
|
||||||
|
FormAction::create('doTriggerException'),
|
||||||
FormAction::create('doSubmitValidationExempt'),
|
FormAction::create('doSubmitValidationExempt'),
|
||||||
FormAction::create('doSubmitActionExempt')
|
FormAction::create('doSubmitActionExempt')
|
||||||
->setValidationExempt(true)
|
->setValidationExempt(true)
|
||||||
@ -75,6 +78,13 @@ class TestController extends Controller implements TestOnly
|
|||||||
return $this->redirectBack();
|
return $this->redirectBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function doTriggerException($data, $form, $request) {
|
||||||
|
$result = new ValidationResult();
|
||||||
|
$result->addFieldError('Email', 'Error on Email field');
|
||||||
|
$result->addError('Error at top of form');
|
||||||
|
throw new ValidationException($result);
|
||||||
|
}
|
||||||
|
|
||||||
public function doSubmitValidationExempt($data, $form, $request)
|
public function doSubmitValidationExempt($data, $form, $request)
|
||||||
{
|
{
|
||||||
$form->sessionMessage('Validation skipped', 'good');
|
$form->sessionMessage('Validation skipped', 'good');
|
||||||
|
@ -54,7 +54,7 @@ class OptionsetFieldTest extends SapphireTest {
|
|||||||
$this->assertTrue($field->validate($validator));
|
$this->assertTrue($field->validate($validator));
|
||||||
|
|
||||||
// ... but should not pass "RequiredFields" validation
|
// ... but should not pass "RequiredFields" validation
|
||||||
$this->assertFalse($form->validate());
|
$this->assertFalse($form->validationResult()->isValid());
|
||||||
|
|
||||||
//disabled items shouldn't validate
|
//disabled items shouldn't validate
|
||||||
$field->setDisabledItems(array('Five'));
|
$field->setDisabledItems(array('Five'));
|
||||||
|
@ -937,7 +937,7 @@ class UploadFieldTest extends FunctionalTest {
|
|||||||
$form = new UploadFieldTest\UploadFieldTestForm();
|
$form = new UploadFieldTest\UploadFieldTestForm();
|
||||||
$form->loadDataFrom($data, true);
|
$form->loadDataFrom($data, true);
|
||||||
|
|
||||||
if($form->validate()) {
|
if($form->validationResult()->isValid()) {
|
||||||
$record = $form->getRecord();
|
$record = $form->getRecord();
|
||||||
$form->saveInto($record);
|
$form->saveInto($record);
|
||||||
$record->write();
|
$record->write();
|
||||||
|
@ -13,14 +13,10 @@ use SilverStripe\ORM\Connect\MySQLDatabase;
|
|||||||
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
|
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
|
||||||
use SilverStripe\ORM\FieldType\DBVarchar;
|
use SilverStripe\ORM\FieldType\DBVarchar;
|
||||||
use SilverStripe\ORM\ManyManyList;
|
use SilverStripe\ORM\ManyManyList;
|
||||||
use SilverStripe\ORM\Tests\ManyManyListTest\Category;
|
|
||||||
use SilverStripe\ORM\Tests\ManyManyListTest\ExtraFieldsObject;
|
|
||||||
use SilverStripe\ORM\Tests\ManyManyListTest\Product;
|
|
||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationException;
|
||||||
use SilverStripe\View\ViewableData;
|
use SilverStripe\View\ViewableData;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use ReflectionException;
|
use ReflectionException;
|
||||||
use ReflectionMethod;
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class DataObjectTest extends SapphireTest {
|
class DataObjectTest extends SapphireTest {
|
||||||
@ -1055,7 +1051,6 @@ class DataObjectTest extends SapphireTest {
|
|||||||
|
|
||||||
public function testWritingInvalidDataObjectThrowsException() {
|
public function testWritingInvalidDataObjectThrowsException() {
|
||||||
$validatedObject = new DataObjectTest\ValidatedObject();
|
$validatedObject = new DataObjectTest\ValidatedObject();
|
||||||
|
|
||||||
$this->setExpectedException(ValidationException::class);
|
$this->setExpectedException(ValidationException::class);
|
||||||
$validatedObject->write();
|
$validatedObject->write();
|
||||||
}
|
}
|
||||||
@ -1181,12 +1176,6 @@ class DataObjectTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function makeAccessible($object, $method) {
|
|
||||||
$reflectionMethod = new ReflectionMethod($object, $method);
|
|
||||||
$reflectionMethod->setAccessible(true);
|
|
||||||
return $reflectionMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testValidateModelDefinitionsFailsWithArray() {
|
public function testValidateModelDefinitionsFailsWithArray() {
|
||||||
Config::inst()->update(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
|
Config::inst()->update(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
|
||||||
$this->setExpectedException(InvalidArgumentException::class);
|
$this->setExpectedException(InvalidArgumentException::class);
|
||||||
|
@ -16,10 +16,10 @@ class ValidatedObject extends DataObject implements TestOnly
|
|||||||
|
|
||||||
public function validate()
|
public function validate()
|
||||||
{
|
{
|
||||||
if (!empty($this->Name)) {
|
$result = ValidationResult::create();
|
||||||
return new ValidationResult();
|
if (empty($this->Name)) {
|
||||||
} else {
|
$result->addError("This object needs a name. Otherwise it will have an identity crisis!");
|
||||||
return new ValidationResult(false, "This object needs a name. Otherwise it will have an identity crisis!");
|
|
||||||
}
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,17 +31,7 @@ class HierarchyTest extends SapphireTest {
|
|||||||
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa');
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa');
|
||||||
|
|
||||||
$obj2->ParentID = $obj2aa->ID;
|
$obj2->ParentID = $obj2aa->ID;
|
||||||
$obj2->write();
|
$obj2->write();
|
||||||
}
|
|
||||||
catch (ValidationException $e) {
|
|
||||||
$this->assertContains(
|
|
||||||
Convert::raw2xml('Infinite loop found within the "HierarchyTest_Object" hierarchy'),
|
|
||||||
$e->getMessage()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->fail('Failed to prevent infinite loop in hierarchy.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,7 +184,7 @@ class HierarchyTest extends SapphireTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers SilverStripe\ORM\Hierarchy\Hierarchy::markChildren()
|
* @covers \SilverStripe\ORM\Hierarchy\Hierarchy::markChildren()
|
||||||
*/
|
*/
|
||||||
public function testMarkChildrenDoesntUnmarkPreviouslyMarked() {
|
public function testMarkChildrenDoesntUnmarkPreviouslyMarked() {
|
||||||
$obj3 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3');
|
$obj3 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3');
|
||||||
|
@ -12,7 +12,6 @@ class ValidationExceptionTest extends SapphireTest
|
|||||||
* Test that ValidationResult object can correctly populate a ValidationException
|
* Test that ValidationResult object can correctly populate a ValidationException
|
||||||
*/
|
*/
|
||||||
public function testCreateFromValidationResult() {
|
public function testCreateFromValidationResult() {
|
||||||
|
|
||||||
$result = new ValidationResult();
|
$result = new ValidationResult();
|
||||||
$result->addError('Not a valid result');
|
$result->addError('Not a valid result');
|
||||||
|
|
||||||
@ -20,8 +19,13 @@ class ValidationExceptionTest extends SapphireTest
|
|||||||
|
|
||||||
$this->assertEquals(0, $exception->getCode());
|
$this->assertEquals(0, $exception->getCode());
|
||||||
$this->assertEquals('Not a valid result', $exception->getMessage());
|
$this->assertEquals('Not a valid result', $exception->getMessage());
|
||||||
$this->assertFalse($exception->getResult()->valid());
|
$this->assertFalse($exception->getResult()->isValid());
|
||||||
$this->assertEquals('Not a valid result', $exception->getResult()->message());
|
$this->assertContains([
|
||||||
|
'message' => 'Not a valid result',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
'messageType' => ValidationResult::TYPE_ERROR,
|
||||||
|
'fieldName' => null,
|
||||||
|
], $exception->getResult()->getMessages());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,14 +35,26 @@ class ValidationExceptionTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testCreateFromComplexValidationResult() {
|
public function testCreateFromComplexValidationResult() {
|
||||||
$result = new ValidationResult();
|
$result = new ValidationResult();
|
||||||
$result->addError('Invalid type')
|
$result
|
||||||
->addError('Out of kiwis');
|
->addError('Invalid type')
|
||||||
|
->addError('Out of kiwis');
|
||||||
$exception = new ValidationException($result);
|
$exception = new ValidationException($result);
|
||||||
|
|
||||||
$this->assertEquals(0, $exception->getCode());
|
$this->assertEquals(0, $exception->getCode());
|
||||||
$this->assertEquals('Invalid type; Out of kiwis', $exception->getMessage());
|
$this->assertEquals('Invalid type', $exception->getMessage());
|
||||||
$this->assertEquals(false, $exception->getResult()->valid());
|
$this->assertEquals(false, $exception->getResult()->isValid());
|
||||||
$this->assertEquals('Invalid type; Out of kiwis', $exception->getResult()->message());
|
$this->assertContains([
|
||||||
|
'message' => 'Invalid type',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
'messageType' => ValidationResult::TYPE_ERROR,
|
||||||
|
'fieldName' => null,
|
||||||
|
], $exception->getResult()->getMessages());
|
||||||
|
$this->assertContains([
|
||||||
|
'message' => 'Out of kiwis',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
'messageType' => ValidationResult::TYPE_ERROR,
|
||||||
|
'fieldName' => null,
|
||||||
|
], $exception->getResult()->getMessages());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,26 +66,15 @@ class ValidationExceptionTest extends SapphireTest
|
|||||||
|
|
||||||
$this->assertEquals(E_USER_ERROR, $exception->getCode());
|
$this->assertEquals(E_USER_ERROR, $exception->getCode());
|
||||||
$this->assertEquals('Error inferred from message', $exception->getMessage());
|
$this->assertEquals('Error inferred from message', $exception->getMessage());
|
||||||
$this->assertFalse($exception->getResult()->valid());
|
$this->assertFalse($exception->getResult()->isValid());
|
||||||
$this->assertEquals('Error inferred from message', $exception->getResult()->message());
|
$this->assertContains([
|
||||||
|
'message' => 'Error inferred from message',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
'messageType' => ValidationResult::TYPE_ERROR,
|
||||||
|
'fieldName' => null,
|
||||||
|
], $exception->getResult()->getMessages());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that ValidationException can be created with both a ValidationResult
|
|
||||||
* and a custom message
|
|
||||||
*/
|
|
||||||
public function testCreateWithValidationResultAndMessage() {
|
|
||||||
$result = new ValidationResult();
|
|
||||||
$result->addError('Incorrect placement of cutlery');
|
|
||||||
$exception = new ValidationException($result, 'An error has occurred', E_USER_WARNING);
|
|
||||||
|
|
||||||
$this->assertEquals(E_USER_WARNING, $exception->getCode());
|
|
||||||
$this->assertEquals('An error has occurred', $exception->getMessage());
|
|
||||||
$this->assertFalse($exception->getResult()->valid());
|
|
||||||
$this->assertEquals('Incorrect placement of cutlery', $exception->getResult()->message());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that ValidationException can be created with both a ValidationResult
|
* Test that ValidationException can be created with both a ValidationResult
|
||||||
* and a custom message
|
* and a custom message
|
||||||
@ -78,13 +83,23 @@ class ValidationExceptionTest extends SapphireTest
|
|||||||
$result = new ValidationResult();
|
$result = new ValidationResult();
|
||||||
$result->addError('A spork is not a knife')
|
$result->addError('A spork is not a knife')
|
||||||
->addError('A knife is not a back scratcher');
|
->addError('A knife is not a back scratcher');
|
||||||
$exception = new ValidationException($result, 'An error has occurred', E_USER_WARNING);
|
$exception = new ValidationException($result, E_USER_WARNING);
|
||||||
|
|
||||||
$this->assertEquals(E_USER_WARNING, $exception->getCode());
|
$this->assertEquals(E_USER_WARNING, $exception->getCode());
|
||||||
$this->assertEquals('An error has occurred', $exception->getMessage());
|
$this->assertEquals('A spork is not a knife', $exception->getMessage());
|
||||||
$this->assertEquals(false, $exception->getResult()->valid());
|
$this->assertEquals(false, $exception->getResult()->isValid());
|
||||||
$this->assertEquals('A spork is not a knife; A knife is not a back scratcher',
|
$this->assertContains([
|
||||||
$exception->getResult()->message());
|
'message' => 'A spork is not a knife',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
'messageType' => ValidationResult::TYPE_ERROR,
|
||||||
|
'fieldName' => null,
|
||||||
|
], $exception->getResult()->getMessages());
|
||||||
|
$this->assertContains([
|
||||||
|
'message' => 'A knife is not a back scratcher',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
'messageType' => ValidationResult::TYPE_ERROR,
|
||||||
|
'fieldName' => null,
|
||||||
|
], $exception->getResult()->getMessages());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,35 +112,30 @@ class ValidationExceptionTest extends SapphireTest
|
|||||||
$anotherresult->addError("Eat with your mouth closed", 'bad', "EATING101");
|
$anotherresult->addError("Eat with your mouth closed", 'bad', "EATING101");
|
||||||
$yetanotherresult->addError("You didn't wash your hands", 'bad', "BECLEAN", false);
|
$yetanotherresult->addError("You didn't wash your hands", 'bad', "BECLEAN", false);
|
||||||
|
|
||||||
$this->assertTrue($result->valid());
|
$this->assertTrue($result->isValid());
|
||||||
$this->assertFalse($anotherresult->valid());
|
$this->assertFalse($anotherresult->isValid());
|
||||||
$this->assertFalse($yetanotherresult->valid());
|
$this->assertFalse($yetanotherresult->isValid());
|
||||||
|
|
||||||
$result->combineAnd($anotherresult)
|
$result->combineAnd($anotherresult)
|
||||||
->combineAnd($yetanotherresult);
|
->combineAnd($yetanotherresult);
|
||||||
$this->assertFalse($result->valid());
|
$this->assertFalse($result->isValid());
|
||||||
$this->assertEquals(array(
|
$this->assertEquals(
|
||||||
"EATING101" => "Eat with your mouth closed",
|
[
|
||||||
"BECLEAN" => "You didn't wash your hands"
|
'EATING101' => [
|
||||||
), $result->messageList());
|
'message' => 'Eat with your mouth closed',
|
||||||
}
|
'messageType' => 'bad',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
/**
|
'fieldName' => null,
|
||||||
* Test that a ValidationException created with no contained ValidationResult
|
],
|
||||||
* will correctly populate itself with an inferred version
|
'BECLEAN' => [
|
||||||
*/
|
'message' => 'You didn\'t wash your hands',
|
||||||
public function testCreateForField() {
|
'messageType' => 'bad',
|
||||||
$exception = ValidationException::create_for_field('Content', 'Content is required');
|
'messageCast' => ValidationResult::CAST_HTML,
|
||||||
|
'fieldName' => null,
|
||||||
$this->assertEquals('Content is required', $exception->getMessage());
|
],
|
||||||
$this->assertEquals(false, $exception->getResult()->valid());
|
],
|
||||||
|
$result->getMessages()
|
||||||
$this->assertEquals(array(
|
);
|
||||||
'Content' => array(
|
|
||||||
'message' => 'Content is required',
|
|
||||||
'messageType' => 'bad',
|
|
||||||
),
|
|
||||||
), $exception->getResult()->fieldErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,23 +147,35 @@ class ValidationExceptionTest extends SapphireTest
|
|||||||
$result->addMessage('A spork is not a knife', 'bad');
|
$result->addMessage('A spork is not a knife', 'bad');
|
||||||
$result->addError('A knife is not a back scratcher');
|
$result->addError('A knife is not a back scratcher');
|
||||||
$result->addFieldMessage('Title', 'Title is good', 'good');
|
$result->addFieldMessage('Title', 'Title is good', 'good');
|
||||||
$result->addFieldError('Content', 'Content is bad');
|
$result->addFieldError('Content', 'Content is bad', 'bad');
|
||||||
|
|
||||||
|
|
||||||
$this->assertEquals(array(
|
$this->assertEquals([
|
||||||
'Title' => array(
|
[
|
||||||
|
'fieldName' => null,
|
||||||
|
'message' => 'A spork is not a knife',
|
||||||
|
'messageType' => 'bad',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fieldName' => null,
|
||||||
|
'message' => 'A knife is not a back scratcher',
|
||||||
|
'messageType' => 'error',
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fieldName' => 'Title',
|
||||||
'message' => 'Title is good',
|
'message' => 'Title is good',
|
||||||
'messageType' => 'good'
|
'messageType' => 'good',
|
||||||
),
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
'Content' => array(
|
],
|
||||||
|
[
|
||||||
|
'fieldName' => 'Content',
|
||||||
'message' => 'Content is bad',
|
'message' => 'Content is bad',
|
||||||
'messageType' => 'bad'
|
'messageType' => 'bad',
|
||||||
)
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
), $result->fieldErrors());
|
]
|
||||||
|
], $result->getMessages());
|
||||||
$this->assertEquals('A spork is not a knife; A knife is not a back scratcher', $result->overallMessage());
|
|
||||||
|
|
||||||
$exception = ValidationException::create_for_field('Content', 'Content is required');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
35
tests/php/ORM/ValidationResultTest.php
Normal file
35
tests/php/ORM/ValidationResultTest.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace SilverStripe\ORM\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
|
|
||||||
|
class ValidationResultTest extends SapphireTest
|
||||||
|
{
|
||||||
|
public function testSerialise() {
|
||||||
|
$result = new ValidationResult();
|
||||||
|
$result->addError("Error", ValidationResult::TYPE_ERROR, null, ValidationResult::CAST_HTML);
|
||||||
|
$result->addMessage("Message", ValidationResult::TYPE_GOOD);
|
||||||
|
$serialised = serialize($result);
|
||||||
|
|
||||||
|
/** @var ValidationResult $result2 */
|
||||||
|
$result2 = unserialize($serialised);
|
||||||
|
$this->assertEquals([
|
||||||
|
[
|
||||||
|
'message' => 'Error',
|
||||||
|
'fieldName' => null,
|
||||||
|
'messageCast' => ValidationResult::CAST_HTML,
|
||||||
|
'messageType' => ValidationResult::TYPE_ERROR,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'Message',
|
||||||
|
'fieldName' => null,
|
||||||
|
'messageCast' => ValidationResult::CAST_TEXT,
|
||||||
|
'messageType' => ValidationResult::TYPE_GOOD,
|
||||||
|
]
|
||||||
|
], $result2->getMessages());
|
||||||
|
$this->assertFalse($result2->isValid());
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Security\Tests;
|
namespace SilverStripe\Security\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Control\Controller;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\Security\Group;
|
use SilverStripe\Security\Group;
|
||||||
use SilverStripe\Dev\FunctionalTest;
|
use SilverStripe\Dev\FunctionalTest;
|
||||||
use SilverStripe\Control\Session;
|
use SilverStripe\Control\Session;
|
||||||
|
use SilverStripe\Security\Permission;
|
||||||
use SilverStripe\Security\Tests\GroupTest\TestMember;
|
use SilverStripe\Security\Tests\GroupTest\TestMember;
|
||||||
use ReflectionMethod;
|
|
||||||
|
|
||||||
class GroupTest extends FunctionalTest {
|
class GroupTest extends FunctionalTest {
|
||||||
|
|
||||||
@ -35,16 +36,17 @@ class GroupTest extends FunctionalTest {
|
|||||||
$this->assertNull($g3->Code, 'Default title doesnt trigger attribute setting');
|
$this->assertNull($g3->Code, 'Default title doesnt trigger attribute setting');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @skipUpgrade
|
||||||
|
*/
|
||||||
public function testMemberGroupRelationForm() {
|
public function testMemberGroupRelationForm() {
|
||||||
Session::set('loggedInAs', $this->idFromFixture(TestMember::class, 'admin'));
|
Session::set('loggedInAs', $this->idFromFixture(TestMember::class, 'admin'));
|
||||||
|
|
||||||
$adminGroup = $this->objFromFixture(Group::class, 'admingroup');
|
$adminGroup = $this->objFromFixture(Group::class, 'admingroup');
|
||||||
$parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
|
$parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
|
||||||
$childGroup = $this->objFromFixture(Group::class, 'childgroup');
|
|
||||||
|
|
||||||
// Test single group relation through checkboxsetfield
|
// Test single group relation through checkboxsetfield
|
||||||
/** @skipUpgrade */
|
$form = new GroupTest\MemberForm(new Controller(), 'Form');
|
||||||
$form = new GroupTest\MemberForm($this, 'Form');
|
|
||||||
$member = $this->objFromFixture(TestMember::class, 'admin');
|
$member = $this->objFromFixture(TestMember::class, 'admin');
|
||||||
$form->loadDataFrom($member);
|
$form->loadDataFrom($member);
|
||||||
$checkboxSetField = $form->Fields()->fieldByName('Groups');
|
$checkboxSetField = $form->Fields()->fieldByName('Groups');
|
||||||
@ -75,9 +77,6 @@ class GroupTest extends FunctionalTest {
|
|||||||
"Removing a previously added toplevel group works"
|
"Removing a previously added toplevel group works"
|
||||||
);
|
);
|
||||||
$this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
|
$this->assertContains($adminGroup->ID, $updatedGroups->column('ID'));
|
||||||
|
|
||||||
// Test adding child group
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnsavedGroups() {
|
public function testUnsavedGroups() {
|
||||||
@ -124,55 +123,47 @@ class GroupTest extends FunctionalTest {
|
|||||||
$childGroupID = $this->idFromFixture(Group::class, 'childgroup');
|
$childGroupID = $this->idFromFixture(Group::class, 'childgroup');
|
||||||
$group->delete();
|
$group->delete();
|
||||||
|
|
||||||
$this->assertEquals(0, DataObject::get(Group::class, "\"ID\" = {$groupID}")->Count(),
|
$this->assertEquals(0, DataObject::get(Group::class, "\"ID\" = {$groupID}")->count(),
|
||||||
'Group is removed');
|
'Group is removed');
|
||||||
$this->assertEquals(0, DataObject::get('SilverStripe\\Security\\Permission', "\"GroupID\" = {$groupID}")->Count(),
|
$this->assertEquals(0, DataObject::get(Permission::class, "\"GroupID\" = {$groupID}")->count(),
|
||||||
'Permissions removed along with the group');
|
'Permissions removed along with the group');
|
||||||
$this->assertEquals(0, DataObject::get(Group::class, "\"ParentID\" = {$groupID}")->Count(),
|
$this->assertEquals(0, DataObject::get(Group::class, "\"ParentID\" = {$groupID}")->count(),
|
||||||
'Child groups are removed');
|
'Child groups are removed');
|
||||||
$this->assertEquals(0, DataObject::get(Group::class, "\"ParentID\" = {$childGroupID}")->Count(),
|
$this->assertEquals(0, DataObject::get(Group::class, "\"ParentID\" = {$childGroupID}")->count(),
|
||||||
'Grandchild groups are removed');
|
'Grandchild groups are removed');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testValidatesPrivilegeLevelOfParent() {
|
public function testValidatesPrivilegeLevelOfParent() {
|
||||||
$nonAdminUser = $this->objFromFixture(TestMember::class, 'childgroupuser');
|
|
||||||
$adminUser = $this->objFromFixture(TestMember::class, 'admin');
|
|
||||||
$nonAdminGroup = $this->objFromFixture(Group::class, 'childgroup');
|
$nonAdminGroup = $this->objFromFixture(Group::class, 'childgroup');
|
||||||
$adminGroup = $this->objFromFixture(Group::class, 'admingroup');
|
$adminGroup = $this->objFromFixture(Group::class, 'admingroup');
|
||||||
|
|
||||||
$nonAdminValidateMethod = new ReflectionMethod($nonAdminGroup, 'validate');
|
|
||||||
$nonAdminValidateMethod->setAccessible(true);
|
|
||||||
|
|
||||||
// Making admin group parent of a non-admin group, effectively expanding is privileges
|
// Making admin group parent of a non-admin group, effectively expanding is privileges
|
||||||
$nonAdminGroup->ParentID = $adminGroup->ID;
|
$nonAdminGroup->ParentID = $adminGroup->ID;
|
||||||
|
|
||||||
$this->logInWithPermission('APPLY_ROLES');
|
$this->logInWithPermission('APPLY_ROLES');
|
||||||
$result = $nonAdminValidateMethod->invoke($nonAdminGroup);
|
$result = $nonAdminGroup->validate();
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$result->valid(),
|
$result->isValid(),
|
||||||
'Members with only APPLY_ROLES can\'t assign parent groups with direct ADMIN permissions'
|
'Members with only APPLY_ROLES can\'t assign parent groups with direct ADMIN permissions'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->logInWithPermission('ADMIN');
|
$this->logInWithPermission('ADMIN');
|
||||||
$result = $nonAdminValidateMethod->invoke($nonAdminGroup);
|
$result = $nonAdminGroup->validate();
|
||||||
$this->assertTrue(
|
$this->assertTrue(
|
||||||
$result->valid(),
|
$result->isValid(),
|
||||||
'Members with ADMIN can assign parent groups with direct ADMIN permissions'
|
'Members with ADMIN can assign parent groups with direct ADMIN permissions'
|
||||||
);
|
);
|
||||||
$nonAdminGroup->write();
|
$nonAdminGroup->write();
|
||||||
$newlyAdminGroup = $nonAdminGroup;
|
|
||||||
|
|
||||||
$this->logInWithPermission('ADMIN');
|
$this->logInWithPermission('ADMIN');
|
||||||
$inheritedAdminGroup = $this->objFromFixture(Group::class, 'group1');
|
$inheritedAdminGroup = $this->objFromFixture(Group::class, 'group1');
|
||||||
$inheritedAdminMethod = new ReflectionMethod($inheritedAdminGroup, 'validate');
|
|
||||||
$inheritedAdminMethod->setAccessible(true);
|
|
||||||
$inheritedAdminGroup->ParentID = $adminGroup->ID;
|
$inheritedAdminGroup->ParentID = $adminGroup->ID;
|
||||||
$inheritedAdminGroup->write(); // only works with ADMIN login
|
$inheritedAdminGroup->write(); // only works with ADMIN login
|
||||||
|
|
||||||
$this->logInWithPermission('APPLY_ROLES');
|
$this->logInWithPermission('APPLY_ROLES');
|
||||||
$result = $inheritedAdminMethod->invoke($nonAdminGroup);
|
$result = $nonAdminGroup->validate();
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$result->valid(),
|
$result->isValid(),
|
||||||
'Members with only APPLY_ROLES can\'t assign parent groups with inherited ADMIN permission'
|
'Members with only APPLY_ROLES can\'t assign parent groups with inherited ADMIN permission'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Security\Tests;
|
|||||||
|
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\Security\PasswordEncryptor;
|
use SilverStripe\Security\PasswordEncryptor;
|
||||||
use SilverStripe\Security\PasswordEncryptor_PHPHash;
|
use SilverStripe\Security\PasswordEncryptor_PHPHash;
|
||||||
use SilverStripe\Security\Security;
|
use SilverStripe\Security\Security;
|
||||||
@ -53,10 +54,11 @@ class MemberAuthenticatorTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
MemberAuthenticator::authenticate($data);
|
MemberAuthenticator::authenticate($data);
|
||||||
|
|
||||||
$member = DataObject::get_by_id(Member::class, $member->ID);
|
/** @var Member $member */
|
||||||
|
$member = DataObject::get_by_id(Member::class, $member->ID);
|
||||||
$this->assertEquals($member->PasswordEncryption, "sha1_v2.4");
|
$this->assertEquals($member->PasswordEncryption, "sha1_v2.4");
|
||||||
$result = $member->checkPassword('mypassword');
|
$result = $member->checkPassword('mypassword');
|
||||||
$this->assertTrue($result->valid());
|
$this->assertTrue($result->isValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNoLegacyPasswordHashMigrationOnIncompatibleAlgorithm() {
|
public function testNoLegacyPasswordHashMigrationOnIncompatibleAlgorithm() {
|
||||||
@ -82,7 +84,7 @@ class MemberAuthenticatorTest extends SapphireTest {
|
|||||||
$member = DataObject::get_by_id(Member::class, $member->ID);
|
$member = DataObject::get_by_id(Member::class, $member->ID);
|
||||||
$this->assertEquals($member->PasswordEncryption, "crc32");
|
$this->assertEquals($member->PasswordEncryption, "crc32");
|
||||||
$result = $member->checkPassword('mypassword');
|
$result = $member->checkPassword('mypassword');
|
||||||
$this->assertTrue($result->valid());
|
$this->assertTrue($result->isValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCustomIdentifierField(){
|
public function testCustomIdentifierField(){
|
||||||
@ -139,10 +141,10 @@ class MemberAuthenticatorTest extends SapphireTest {
|
|||||||
'tempid' => $tempID,
|
'tempid' => $tempID,
|
||||||
'Password' => 'mypassword'
|
'Password' => 'mypassword'
|
||||||
), $form);
|
), $form);
|
||||||
$form->setupFormErrors();
|
$form->restoreFormState();
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals($result->ID, $member->ID);
|
$this->assertEquals($result->ID, $member->ID);
|
||||||
$this->assertEmpty($form->Message());
|
$this->assertEmpty($form->getMessage());
|
||||||
|
|
||||||
// Test incorrect login
|
// Test incorrect login
|
||||||
$form->clearMessage();
|
$form->clearMessage();
|
||||||
@ -150,10 +152,11 @@ class MemberAuthenticatorTest extends SapphireTest {
|
|||||||
'tempid' => $tempID,
|
'tempid' => $tempID,
|
||||||
'Password' => 'notmypassword'
|
'Password' => 'notmypassword'
|
||||||
), $form);
|
), $form);
|
||||||
$form->setupFormErrors();
|
$form->restoreFormState();
|
||||||
$this->assertEmpty($result);
|
$this->assertEmpty($result);
|
||||||
$this->assertEquals(Convert::raw2xml(_t('Member.ERRORWRONGCRED')), $form->Message());
|
$this->assertEquals(_t('Member.ERRORWRONGCRED'), $form->getMessage());
|
||||||
$this->assertEquals('bad', $form->MessageType());
|
$this->assertEquals(ValidationResult::TYPE_ERROR, $form->getMessageType());
|
||||||
|
$this->assertEquals(ValidationResult::CAST_TEXT, $form->getMessageCast());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,10 +173,10 @@ class MemberAuthenticatorTest extends SapphireTest {
|
|||||||
'Email' => 'admin',
|
'Email' => 'admin',
|
||||||
'Password' => 'password'
|
'Password' => 'password'
|
||||||
), $form);
|
), $form);
|
||||||
$form->setupFormErrors();
|
$form->restoreFormState();
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals($result->Email, Security::default_admin_username());
|
$this->assertEquals($result->Email, Security::default_admin_username());
|
||||||
$this->assertEmpty($form->Message());
|
$this->assertEmpty($form->getMessage());
|
||||||
|
|
||||||
// Test incorrect login
|
// Test incorrect login
|
||||||
$form->clearMessage();
|
$form->clearMessage();
|
||||||
@ -181,10 +184,14 @@ class MemberAuthenticatorTest extends SapphireTest {
|
|||||||
'Email' => 'admin',
|
'Email' => 'admin',
|
||||||
'Password' => 'notmypassword'
|
'Password' => 'notmypassword'
|
||||||
), $form);
|
), $form);
|
||||||
$form->setupFormErrors();
|
$form->restoreFormState();
|
||||||
$this->assertEmpty($result);
|
$this->assertEmpty($result);
|
||||||
$this->assertEquals('The provided details don't seem to be correct. Please try again.', $form->Message());
|
$this->assertEquals(
|
||||||
$this->assertEquals('bad', $form->MessageType());
|
'The provided details don\'t seem to be correct. Please try again.',
|
||||||
|
$form->getMessage()
|
||||||
|
);
|
||||||
|
$this->assertEquals(ValidationResult::TYPE_ERROR, $form->getMessageType());
|
||||||
|
$this->assertEquals(ValidationResult::CAST_TEXT, $form->getMessageCast());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefaultAdminLockOut()
|
public function testDefaultAdminLockOut()
|
||||||
|
@ -87,6 +87,6 @@ class MemberCsvBulkLoaderTest extends SapphireTest {
|
|||||||
// TODO Direct getter doesn't work, wtf!
|
// TODO Direct getter doesn't work, wtf!
|
||||||
$this->assertEquals(Security::config()->password_encryption_algorithm, $member->getField('PasswordEncryption'));
|
$this->assertEquals(Security::config()->password_encryption_algorithm, $member->getField('PasswordEncryption'));
|
||||||
$result = $member->checkPassword('mypassword');
|
$result = $member->checkPassword('mypassword');
|
||||||
$this->assertTrue($result->valid());
|
$this->assertTrue($result->isValid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Security\Tests;
|
namespace SilverStripe\Security\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Convert;
|
||||||
use SilverStripe\Core\Object;
|
use SilverStripe\Core\Object;
|
||||||
use SilverStripe\Dev\FunctionalTest;
|
use SilverStripe\Dev\FunctionalTest;
|
||||||
use SilverStripe\Control\Cookie;
|
use SilverStripe\Control\Cookie;
|
||||||
@ -131,7 +132,7 @@ class MemberTest extends FunctionalTest {
|
|||||||
'sha1_v2.4'
|
'sha1_v2.4'
|
||||||
);
|
);
|
||||||
$result = $member->checkPassword('mynewpassword');
|
$result = $member->checkPassword('mynewpassword');
|
||||||
$this->assertTrue($result->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
Security::config()->password_encryption_algorithm = $origAlgo;
|
Security::config()->password_encryption_algorithm = $origAlgo;
|
||||||
}
|
}
|
||||||
@ -150,7 +151,7 @@ class MemberTest extends FunctionalTest {
|
|||||||
'sha1_v2.4'
|
'sha1_v2.4'
|
||||||
);
|
);
|
||||||
$result = $member->checkPassword('');
|
$result = $member->checkPassword('');
|
||||||
$this->assertTrue($result->valid());
|
$this->assertTrue($result->isValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetPassword() {
|
public function testSetPassword() {
|
||||||
@ -158,7 +159,7 @@ class MemberTest extends FunctionalTest {
|
|||||||
$member->Password = "test1";
|
$member->Password = "test1";
|
||||||
$member->write();
|
$member->write();
|
||||||
$result = $member->checkPassword('test1');
|
$result = $member->checkPassword('test1');
|
||||||
$this->assertTrue($result->valid());
|
$this->assertTrue($result->isValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,7 +213,7 @@ class MemberTest extends FunctionalTest {
|
|||||||
$member = $this->objFromFixture(Member::class, 'test');
|
$member = $this->objFromFixture(Member::class, 'test');
|
||||||
$this->assertNotNull($member);
|
$this->assertNotNull($member);
|
||||||
$valid = $member->changePassword('32asDF##$$%%');
|
$valid = $member->changePassword('32asDF##$$%%');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($valid->isValid());
|
||||||
|
|
||||||
$this->assertEmailSent('testuser@example.com', null, 'Your password has been changed',
|
$this->assertEmailSent('testuser@example.com', null, 'Your password has been changed',
|
||||||
'/testuser@example\.com/');
|
'/testuser@example\.com/');
|
||||||
@ -250,80 +251,81 @@ class MemberTest extends FunctionalTest {
|
|||||||
* - at least 7 characters long
|
* - at least 7 characters long
|
||||||
*/
|
*/
|
||||||
public function testValidatePassword() {
|
public function testValidatePassword() {
|
||||||
$member = $this->objFromFixture(Member::class, 'test');
|
/** @var Member $member */
|
||||||
|
$member = $this->objFromFixture(Member::class, 'test');
|
||||||
$this->assertNotNull($member);
|
$this->assertNotNull($member);
|
||||||
|
|
||||||
Member::set_password_validator(new MemberTest\TestPasswordValidator());
|
Member::set_password_validator(new MemberTest\TestPasswordValidator());
|
||||||
|
|
||||||
// BAD PASSWORDS
|
// BAD PASSWORDS
|
||||||
|
|
||||||
$valid = $member->changePassword('shorty');
|
$result = $member->changePassword('shorty');
|
||||||
$this->assertFalse($valid->valid());
|
$this->assertFalse($result->isValid());
|
||||||
$this->assertContains("TOO_SHORT", $valid->codeList());
|
$this->assertArrayHasKey("TOO_SHORT", $result->getMessages());
|
||||||
|
|
||||||
$valid = $member->changePassword('longone');
|
$result = $member->changePassword('longone');
|
||||||
$this->assertNotContains("TOO_SHORT", $valid->codeList());
|
$this->assertArrayNotHasKey("TOO_SHORT", $result->getMessages());
|
||||||
$this->assertContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
|
$this->assertArrayHasKey("LOW_CHARACTER_STRENGTH", $result->getMessages());
|
||||||
$this->assertFalse($valid->valid());
|
$this->assertFalse($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('w1thNumb3rs');
|
$result = $member->changePassword('w1thNumb3rs');
|
||||||
$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
|
$this->assertArrayNotHasKey("LOW_CHARACTER_STRENGTH", $result->getMessages());
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
// Clear out the MemberPassword table to ensure that the system functions properly in that situation
|
// Clear out the MemberPassword table to ensure that the system functions properly in that situation
|
||||||
DB::query("DELETE FROM \"MemberPassword\"");
|
DB::query("DELETE FROM \"MemberPassword\"");
|
||||||
|
|
||||||
// GOOD PASSWORDS
|
// GOOD PASSWORDS
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls');
|
$result = $member->changePassword('withSym###Ls');
|
||||||
$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
|
$this->assertArrayNotHasKey("LOW_CHARACTER_STRENGTH", $result->getMessages());
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls2');
|
$result = $member->changePassword('withSym###Ls2');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls3');
|
$result = $member->changePassword('withSym###Ls3');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls4');
|
$result = $member->changePassword('withSym###Ls4');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls5');
|
$result = $member->changePassword('withSym###Ls5');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls6');
|
$result = $member->changePassword('withSym###Ls6');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls7');
|
$result = $member->changePassword('withSym###Ls7');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
// CAN'T USE PASSWORDS 2-7, but I can use pasword 1
|
// CAN'T USE PASSWORDS 2-7, but I can use pasword 1
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls2');
|
$result = $member->changePassword('withSym###Ls2');
|
||||||
$this->assertFalse($valid->valid());
|
$this->assertFalse($result->isValid());
|
||||||
$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
|
$this->assertArrayHasKey("PREVIOUS_PASSWORD", $result->getMessages());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls5');
|
$result = $member->changePassword('withSym###Ls5');
|
||||||
$this->assertFalse($valid->valid());
|
$this->assertFalse($result->isValid());
|
||||||
$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
|
$this->assertArrayHasKey("PREVIOUS_PASSWORD", $result->getMessages());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls7');
|
$result = $member->changePassword('withSym###Ls7');
|
||||||
$this->assertFalse($valid->valid());
|
$this->assertFalse($result->isValid());
|
||||||
$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
|
$this->assertArrayHasKey("PREVIOUS_PASSWORD", $result->getMessages());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls');
|
$result = $member->changePassword('withSym###Ls');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
// HAVING DONE THAT, PASSWORD 2 is now available from the list
|
// HAVING DONE THAT, PASSWORD 2 is now available from the list
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls2');
|
$result = $member->changePassword('withSym###Ls2');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls3');
|
$result = $member->changePassword('withSym###Ls3');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
$valid = $member->changePassword('withSym###Ls4');
|
$result = $member->changePassword('withSym###Ls4');
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($result->isValid());
|
||||||
|
|
||||||
Member::set_password_validator(null);
|
Member::set_password_validator(null);
|
||||||
}
|
}
|
||||||
@ -337,14 +339,14 @@ class MemberTest extends FunctionalTest {
|
|||||||
$member = $this->objFromFixture(Member::class, 'test');
|
$member = $this->objFromFixture(Member::class, 'test');
|
||||||
$this->assertNotNull($member);
|
$this->assertNotNull($member);
|
||||||
$valid = $member->changePassword("Xx?1234234");
|
$valid = $member->changePassword("Xx?1234234");
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($valid->isValid());
|
||||||
|
|
||||||
$expiryDate = date('Y-m-d', time() + 90*86400);
|
$expiryDate = date('Y-m-d', time() + 90*86400);
|
||||||
$this->assertEquals($expiryDate, $member->PasswordExpiry);
|
$this->assertEquals($expiryDate, $member->PasswordExpiry);
|
||||||
|
|
||||||
Member::config()->password_expiry_days = null;
|
Member::config()->password_expiry_days = null;
|
||||||
$valid = $member->changePassword("Xx?1234235");
|
$valid = $member->changePassword("Xx?1234235");
|
||||||
$this->assertTrue($valid->valid());
|
$this->assertTrue($valid->isValid());
|
||||||
|
|
||||||
$this->assertNull($member->PasswordExpiry);
|
$this->assertNull($member->PasswordExpiry);
|
||||||
}
|
}
|
||||||
@ -870,11 +872,11 @@ class MemberTest extends FunctionalTest {
|
|||||||
'alc_device' => $firstHash->DeviceID
|
'alc_device' => $firstHash->DeviceID
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$message = _t(
|
$message = Convert::raw2xml(_t(
|
||||||
'Member.LOGGEDINAS',
|
'Member.LOGGEDINAS',
|
||||||
"You're logged in as {name}.",
|
"You're logged in as {name}.",
|
||||||
array('name' => $m1->FirstName)
|
array('name' => $m1->FirstName)
|
||||||
);
|
));
|
||||||
$this->assertContains($message, $response->getBody());
|
$this->assertContains($message, $response->getBody());
|
||||||
|
|
||||||
$this->session()->inst_set('loggedInAs', null);
|
$this->session()->inst_set('loggedInAs', null);
|
||||||
@ -924,9 +926,9 @@ class MemberTest extends FunctionalTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testExpiredRememberMeHashAutologin() {
|
public function testExpiredRememberMeHashAutologin() {
|
||||||
|
/** @var Member $m1 */
|
||||||
$m1 = $this->objFromFixture(Member::class, 'noexpiry');
|
$m1 = $this->objFromFixture(Member::class, 'noexpiry');
|
||||||
|
$m1->logIn(true);
|
||||||
$m1->login(true);
|
|
||||||
$firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->first();
|
$firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->first();
|
||||||
$this->assertNotNull($firstHash);
|
$this->assertNotNull($firstHash);
|
||||||
|
|
||||||
@ -936,7 +938,7 @@ class MemberTest extends FunctionalTest {
|
|||||||
$firstHash->ExpiryDate = '2000-01-01 00:00:00';
|
$firstHash->ExpiryDate = '2000-01-01 00:00:00';
|
||||||
$firstHash->write();
|
$firstHash->write();
|
||||||
|
|
||||||
DBDateTime::set_mock_now('1999-12-31 23:59:59');
|
DBDatetime::set_mock_now('1999-12-31 23:59:59');
|
||||||
|
|
||||||
$response = $this->get(
|
$response = $this->get(
|
||||||
'Security/login',
|
'Security/login',
|
||||||
@ -947,11 +949,11 @@ class MemberTest extends FunctionalTest {
|
|||||||
'alc_device' => $firstHash->DeviceID
|
'alc_device' => $firstHash->DeviceID
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$message = _t(
|
$message = Convert::raw2xml(_t(
|
||||||
'Member.LOGGEDINAS',
|
'Member.LOGGEDINAS',
|
||||||
"You're logged in as {name}.",
|
"You're logged in as {name}.",
|
||||||
array('name' => $m1->FirstName)
|
array('name' => $m1->FirstName)
|
||||||
);
|
));
|
||||||
$this->assertContains($message, $response->getBody());
|
$this->assertContains($message, $response->getBody());
|
||||||
|
|
||||||
$this->session()->inst_set('loggedInAs', null);
|
$this->session()->inst_set('loggedInAs', null);
|
||||||
@ -1017,11 +1019,11 @@ class MemberTest extends FunctionalTest {
|
|||||||
'alc_device' => $firstHash->DeviceID
|
'alc_device' => $firstHash->DeviceID
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$message = _t(
|
$message = Convert::raw2xml(_t(
|
||||||
'Member.LOGGEDINAS',
|
'Member.LOGGEDINAS',
|
||||||
"You're logged in as {name}.",
|
"You're logged in as {name}.",
|
||||||
array('name' => $m1->FirstName)
|
array('name' => $m1->FirstName)
|
||||||
);
|
));
|
||||||
$this->assertContains($message, $response->getBody());
|
$this->assertContains($message, $response->getBody());
|
||||||
|
|
||||||
$this->session()->inst_set('loggedInAs', null);
|
$this->session()->inst_set('loggedInAs', null);
|
||||||
|
@ -11,10 +11,10 @@ class PasswordValidatorTest extends SapphireTest {
|
|||||||
public function testValidate() {
|
public function testValidate() {
|
||||||
$v = new PasswordValidator();
|
$v = new PasswordValidator();
|
||||||
$r = $v->validate('', new Member());
|
$r = $v->validate('', new Member());
|
||||||
$this->assertTrue($r->valid(), 'Empty password is valid by default');
|
$this->assertTrue($r->isValid(), 'Empty password is valid by default');
|
||||||
|
|
||||||
$r = $v->validate('mypassword', new Member());
|
$r = $v->validate('mypassword', new Member());
|
||||||
$this->assertTrue($r->valid(), 'Non-Empty password is valid by default');
|
$this->assertTrue($r->isValid(), 'Non-Empty password is valid by default');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testValidateMinLength() {
|
public function testValidateMinLength() {
|
||||||
@ -22,11 +22,11 @@ class PasswordValidatorTest extends SapphireTest {
|
|||||||
|
|
||||||
$v->minLength(4);
|
$v->minLength(4);
|
||||||
$r = $v->validate('123', new Member());
|
$r = $v->validate('123', new Member());
|
||||||
$this->assertFalse($r->valid(), 'Password too short');
|
$this->assertFalse($r->isValid(), 'Password too short');
|
||||||
|
|
||||||
$v->minLength(4);
|
$v->minLength(4);
|
||||||
$r = $v->validate('1234', new Member());
|
$r = $v->validate('1234', new Member());
|
||||||
$this->assertTrue($r->valid(), 'Password long enough');
|
$this->assertTrue($r->isValid(), 'Password long enough');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testValidateMinScore() {
|
public function testValidateMinScore() {
|
||||||
@ -34,10 +34,10 @@ class PasswordValidatorTest extends SapphireTest {
|
|||||||
$v->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
$v->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
||||||
|
|
||||||
$r = $v->validate('aA', new Member());
|
$r = $v->validate('aA', new Member());
|
||||||
$this->assertFalse($r->valid(), 'Passing too few tests');
|
$this->assertFalse($r->isValid(), 'Passing too few tests');
|
||||||
|
|
||||||
$r = $v->validate('aA1', new Member());
|
$r = $v->validate('aA1', new Member());
|
||||||
$this->assertTrue($r->valid(), 'Passing enough tests');
|
$this->assertTrue($r->isValid(), 'Passing enough tests');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHistoricalPasswordCount() {
|
public function testHistoricalPasswordCount() {
|
||||||
|
@ -6,7 +6,6 @@ use SilverStripe\ORM\DataObject;
|
|||||||
use SilverStripe\Security\PermissionRole;
|
use SilverStripe\Security\PermissionRole;
|
||||||
use SilverStripe\Security\PermissionRoleCode;
|
use SilverStripe\Security\PermissionRoleCode;
|
||||||
use SilverStripe\Dev\FunctionalTest;
|
use SilverStripe\Dev\FunctionalTest;
|
||||||
use ReflectionMethod;
|
|
||||||
|
|
||||||
class PermissionRoleTest extends FunctionalTest {
|
class PermissionRoleTest extends FunctionalTest {
|
||||||
protected static $fixture_file = 'PermissionRoleTest.yml';
|
protected static $fixture_file = 'PermissionRoleTest.yml';
|
||||||
@ -24,31 +23,26 @@ class PermissionRoleTest extends FunctionalTest {
|
|||||||
|
|
||||||
public function testValidatesPrivilegedPermissions() {
|
public function testValidatesPrivilegedPermissions() {
|
||||||
$nonAdminCode = new PermissionRoleCode(array('Code' => 'CMS_ACCESS_CMSMain'));
|
$nonAdminCode = new PermissionRoleCode(array('Code' => 'CMS_ACCESS_CMSMain'));
|
||||||
$nonAdminValidateMethod = new ReflectionMethod($nonAdminCode, 'validate');
|
|
||||||
$nonAdminValidateMethod->setAccessible(true);
|
|
||||||
|
|
||||||
$adminCode = new PermissionRoleCode(array('Code' => 'ADMIN'));
|
$adminCode = new PermissionRoleCode(array('Code' => 'ADMIN'));
|
||||||
$adminValidateMethod = new ReflectionMethod($adminCode, 'validate');
|
|
||||||
$adminValidateMethod->setAccessible(true);
|
|
||||||
|
|
||||||
$this->logInWithPermission('APPLY_ROLES');
|
$this->logInWithPermission('APPLY_ROLES');
|
||||||
$result = $nonAdminValidateMethod->invoke($nonAdminCode);
|
$result = $nonAdminCode->validate();
|
||||||
$this->assertTrue(
|
$this->assertTrue(
|
||||||
$result->valid(),
|
$result->isValid(),
|
||||||
'Members with only APPLY_ROLES can create non-privileged permission role codes'
|
'Members with only APPLY_ROLES can create non-privileged permission role codes'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->logInWithPermission('APPLY_ROLES');
|
$this->logInWithPermission('APPLY_ROLES');
|
||||||
$result = $adminValidateMethod->invoke($adminCode);
|
$result = $adminCode->validate();
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$result->valid(),
|
$result->isValid(),
|
||||||
'Members with only APPLY_ROLES can\'t create privileged permission role codes'
|
'Members with only APPLY_ROLES can\'t create privileged permission role codes'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->logInWithPermission('ADMIN');
|
$this->logInWithPermission('ADMIN');
|
||||||
$result = $adminValidateMethod->invoke($adminCode);
|
$result = $adminCode->validate();
|
||||||
$this->assertTrue(
|
$this->assertTrue(
|
||||||
$result->valid(),
|
$result->isValid(),
|
||||||
'Members with ADMIN can create privileged permission role codes'
|
'Members with ADMIN can create privileged permission role codes'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use SilverStripe\ORM\DataObject;
|
|||||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||||
use SilverStripe\ORM\FieldType\DBClassName;
|
use SilverStripe\ORM\FieldType\DBClassName;
|
||||||
use SilverStripe\ORM\DB;
|
use SilverStripe\ORM\DB;
|
||||||
|
use SilverStripe\ORM\ValidationResult;
|
||||||
use SilverStripe\Security\Authenticator;
|
use SilverStripe\Security\Authenticator;
|
||||||
use SilverStripe\Security\LoginAttempt;
|
use SilverStripe\Security\LoginAttempt;
|
||||||
use SilverStripe\Security\Member;
|
use SilverStripe\Security\Member;
|
||||||
@ -444,7 +445,10 @@ class SecurityTest extends FunctionalTest {
|
|||||||
$member->LockedOutUntil,
|
$member->LockedOutUntil,
|
||||||
'User does not have a lockout time set if under threshold for failed attempts'
|
'User does not have a lockout time set if under threshold for failed attempts'
|
||||||
);
|
);
|
||||||
$this->assertContains(Convert::raw2xml(_t('Member.ERRORWRONGCRED')), $this->loginErrorMessage());
|
$this->assertHasMessage(_t(
|
||||||
|
'Member.ERRORWRONGCRED',
|
||||||
|
'The provided details don\'t seem to be correct. Please try again.'
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
// Fuzzy matching for time to avoid side effects from slow running tests
|
// Fuzzy matching for time to avoid side effects from slow running tests
|
||||||
$this->assertGreaterThan(
|
$this->assertGreaterThan(
|
||||||
@ -462,7 +466,7 @@ class SecurityTest extends FunctionalTest {
|
|||||||
array('count' => Member::config()->lock_out_delay_mins)
|
array('count' => Member::config()->lock_out_delay_mins)
|
||||||
);
|
);
|
||||||
if($i > Member::config()->lock_out_after_incorrect_logins) {
|
if($i > Member::config()->lock_out_after_incorrect_logins) {
|
||||||
$this->assertContains($msg, $this->loginErrorMessage());
|
$this->assertHasMessage($msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,9 +495,8 @@ class SecurityTest extends FunctionalTest {
|
|||||||
$this->doTestLoginForm('testuser@example.com' , 'incorrectpassword');
|
$this->doTestLoginForm('testuser@example.com' , 'incorrectpassword');
|
||||||
}
|
}
|
||||||
$this->assertNull($this->session()->inst_get('loggedInAs'));
|
$this->assertNull($this->session()->inst_get('loggedInAs'));
|
||||||
$this->assertContains(
|
$this->assertHasMessage(
|
||||||
$this->loginErrorMessage(),
|
_t('Member.ERRORWRONGCRED','The provided details don\'t seem to be correct. Please try again.'),
|
||||||
Convert::raw2xml(_t('Member.ERRORWRONGCRED')),
|
|
||||||
'The user can retry with a wrong password after the lockout expires'
|
'The user can retry with a wrong password after the lockout expires'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -560,9 +563,7 @@ class SecurityTest extends FunctionalTest {
|
|||||||
$this->assertTrue(is_object($attempt));
|
$this->assertTrue(is_object($attempt));
|
||||||
$this->assertEquals($attempt->Status, 'Failure');
|
$this->assertEquals($attempt->Status, 'Failure');
|
||||||
$this->assertEquals($attempt->Email, 'wronguser@silverstripe.com');
|
$this->assertEquals($attempt->Email, 'wronguser@silverstripe.com');
|
||||||
$this->assertNotNull(
|
$this->assertNotEmpty($this->getValidationResult()->getMessages(), 'An invalid email returns a message.');
|
||||||
$this->loginErrorMessage(), 'An invalid email returns a message.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSuccessfulLoginAttempts() {
|
public function testSuccessfulLoginAttempts() {
|
||||||
@ -640,12 +641,35 @@ class SecurityTest extends FunctionalTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the error message on the login form
|
* Assert this message is in the current login form errors
|
||||||
*/
|
*
|
||||||
public function loginErrorMessage() {
|
* @param string $expected
|
||||||
$result = $this->session()->inst_get('FormInfo.MemberLoginForm_LoginForm.result');
|
* @param string $errorMessage
|
||||||
return $result->message();
|
*/
|
||||||
}
|
protected function assertHasMessage($expected, $errorMessage = null) {
|
||||||
|
$messages = [];
|
||||||
|
$result = $this->getValidationResult();
|
||||||
|
if ($result) {
|
||||||
|
foreach($result->getMessages() as $message) {
|
||||||
|
$messages[] = $message['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertContains($expected, $messages, $errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get validation result from last login form submission
|
||||||
|
*
|
||||||
|
* @return ValidationResult
|
||||||
|
*/
|
||||||
|
protected function getValidationResult() {
|
||||||
|
$result = $this->session()->inst_get('FormInfo.MemberLoginForm_LoginForm.result');
|
||||||
|
if ($result) {
|
||||||
|
/** @var ValidationResult $resultObj */
|
||||||
|
return unserialize($result);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user