mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge branch '3'
This commit is contained in:
commit
bcc21c2403
@ -10,10 +10,10 @@ addons:
|
|||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- TRAVIS_NODE_VERSION="4"
|
- TRAVIS_NODE_VERSION="4"
|
||||||
- ARTIFACTS_REGION=us-east-1
|
- "ARTIFACTS_AWS_REGION=us-east-1"
|
||||||
- ARTIFACTS_BUCKET=silverstripe-travis-artifacts
|
- "ARTIFACTS_S3_BUCKET=silverstripe-travis-artifacts"
|
||||||
- secure: "jVR0iLTuvVfA6jKX5+A3AdUEs8Ps+r3SbL0zGR687K8IoSp3a/+JLH12zFCEexOuxwCtOhlMq8zoZsptCEduCDq+0payk5k6GjNVywFaWjJCV573JScdaHAtoumoHMUvua+Pxds0qKAD2XEYAcOR4Qu7S4HLJV6E1QqHg9PRW5s=" # Encrypted ARTIFACTS_KEY
|
- secure: "DjwZKhY/c0wXppGmd8oEMiTV0ayfOXiCmi9Lg1aXoSXNnj+sjLmhYwhUWjehjR6IX0MRtzJG6v7V5Y+4nSGe+i+XIrBQnhPQ95Jrkm1gKofX2mznWTl9npQElNS1DXi58NLPbiB3qxHWGFBRAWmRQrsAouyZabkPnChnSa9ldOg="
|
||||||
- secure: "SDGv49c2Ee2YBz7dATE3WnHSVSvJiRJ2BVtRasVshdNDNz3NBRzh13C2fDwTGBU1J6PxiQaGTXBy/BGsvbYk2BvdzHVwozkBpHVSaCNdarpCJ5yZZTqKC3mpA1S5353r5tqronwFuMDpftzXnRMfLZGGQ4kYb9hjV55+FPUTFPk=" # Encrypted ARTIFACTS_SECRET
|
- secure: "UmbXCNLK0f2Dk+7qX8bOVcgIt4QhRvccoWvMUxaPtIU+95HCbG10eeCxvfOeBax+tHcRXmeCG4vM4tcuT/WoANkAma/VX74DylFjbWhks2tsKOcr2kjTrOwe6Q9CXOBjVAlcx0lnV/a+w83KARjXGnCrIbE7p7r4EDw31rkVufg="
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
@ -34,6 +34,7 @@ matrix:
|
|||||||
env: DB=MYSQL CMS_TEST=1 BEHAT_TEST=1
|
env: DB=MYSQL CMS_TEST=1 BEHAT_TEST=1
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
- export CORE_RELEASE=$TRAVIS_BRANCH
|
||||||
- printf "\n" | pecl install imagick
|
- printf "\n" | pecl install imagick
|
||||||
- composer self-update || true
|
- composer self-update || true
|
||||||
- phpenv rehash
|
- phpenv rehash
|
||||||
|
@ -36,6 +36,8 @@ SilverStripe\Core\Injector\Injector:
|
|||||||
class: SilverStripe\ORM\FieldType\DBHTMLVarchar
|
class: SilverStripe\ORM\FieldType\DBHTMLVarchar
|
||||||
Int:
|
Int:
|
||||||
class: SilverStripe\ORM\FieldType\DBInt
|
class: SilverStripe\ORM\FieldType\DBInt
|
||||||
|
BigInt:
|
||||||
|
class: SilverStripe\ORM\FieldType\DBBigInt
|
||||||
Locale:
|
Locale:
|
||||||
class: SilverStripe\ORM\FieldType\DBLocale
|
class: SilverStripe\ORM\FieldType\DBLocale
|
||||||
DBLocale:
|
DBLocale:
|
||||||
|
89
admin/client/dist/js/bundle.js
vendored
89
admin/client/dist/js/bundle.js
vendored
@ -58,64 +58,69 @@ return this.fetch(e,{method:"put",credentials:"same-origin",body:s(t),headers:n}
|
|||||||
return this.fetch(e,{method:"delete",credentials:"same-origin",body:s(t),headers:n}).then(a)}}]),e}(),O=new P
|
return this.fetch(e,{method:"delete",credentials:"same-origin",body:s(t),headers:n}).then(a)}}]),e}(),O=new P
|
||||||
t["default"]=O},function(e,t,n){n(8),e.exports=self.fetch.bind(self)},function(e,t){!function(e){"use strict"
|
t["default"]=O},function(e,t,n){n(8),e.exports=self.fetch.bind(self)},function(e,t){!function(e){"use strict"
|
||||||
function t(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name")
|
function t(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name")
|
||||||
return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function i(e){this.map={},e instanceof i?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){
|
return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function i(e){var t={next:function(){var t=e.shift()
|
||||||
this.append(t,e[t])},this)}function r(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function o(e){return new Promise(function(t,n){e.onload=function(){t(e.result)
|
return{done:void 0===t,value:t}}}
|
||||||
|
return m.iterable&&(t[Symbol.iterator]=function(){return t}),t}function r(e){this.map={},e instanceof r?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){
|
||||||
|
this.append(t,e[t])},this)}function o(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function a(e){return new Promise(function(t,n){e.onload=function(){t(e.result)
|
||||||
|
|
||||||
},e.onerror=function(){n(e.error)}})}function a(e){var t=new FileReader
|
},e.onerror=function(){n(e.error)}})}function s(e){var t=new FileReader
|
||||||
return t.readAsArrayBuffer(e),o(t)}function s(e){var t=new FileReader
|
return t.readAsArrayBuffer(e),a(t)}function l(e){var t=new FileReader
|
||||||
return t.readAsText(e),o(t)}function l(){return this.bodyUsed=!1,this._initBody=function(e){if(this._bodyInit=e,"string"==typeof e)this._bodyText=e
|
return t.readAsText(e),a(t)}function u(){return this.bodyUsed=!1,this._initBody=function(e){if(this._bodyInit=e,"string"==typeof e)this._bodyText=e
|
||||||
else if(h.blob&&Blob.prototype.isPrototypeOf(e))this._bodyBlob=e
|
else if(m.blob&&Blob.prototype.isPrototypeOf(e))this._bodyBlob=e
|
||||||
else if(h.formData&&FormData.prototype.isPrototypeOf(e))this._bodyFormData=e
|
else if(m.formData&&FormData.prototype.isPrototypeOf(e))this._bodyFormData=e
|
||||||
else if(e){if(!h.arrayBuffer||!ArrayBuffer.prototype.isPrototypeOf(e))throw new Error("unsupported BodyInit type")}else this._bodyText=""
|
else if(m.searchParams&&URLSearchParams.prototype.isPrototypeOf(e))this._bodyText=e.toString()
|
||||||
this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type&&this.headers.set("content-type",this._bodyBlob.type))
|
else if(e){if(!m.arrayBuffer||!ArrayBuffer.prototype.isPrototypeOf(e))throw new Error("unsupported BodyInit type")}else this._bodyText=""
|
||||||
|
this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):m.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))
|
||||||
|
|
||||||
},h.blob?(this.blob=function(){var e=r(this)
|
},m.blob?(this.blob=function(){var e=o(this)
|
||||||
if(e)return e
|
if(e)return e
|
||||||
if(this._bodyBlob)return Promise.resolve(this._bodyBlob)
|
if(this._bodyBlob)return Promise.resolve(this._bodyBlob)
|
||||||
if(this._bodyFormData)throw new Error("could not read FormData body as blob")
|
if(this._bodyFormData)throw new Error("could not read FormData body as blob")
|
||||||
return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this.blob().then(a)},this.text=function(){var e=r(this)
|
return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this.blob().then(s)},this.text=function(){var e=o(this)
|
||||||
if(e)return e
|
if(e)return e
|
||||||
if(this._bodyBlob)return s(this._bodyBlob)
|
if(this._bodyBlob)return l(this._bodyBlob)
|
||||||
if(this._bodyFormData)throw new Error("could not read FormData body as text")
|
if(this._bodyFormData)throw new Error("could not read FormData body as text")
|
||||||
return Promise.resolve(this._bodyText)}):this.text=function(){var e=r(this)
|
return Promise.resolve(this._bodyText)}):this.text=function(){var e=o(this)
|
||||||
return e?e:Promise.resolve(this._bodyText)},h.formData&&(this.formData=function(){return this.text().then(d)}),this.json=function(){return this.text().then(JSON.parse)},this}function u(e){var t=e.toUpperCase()
|
return e?e:Promise.resolve(this._bodyText)},m.formData&&(this.formData=function(){return this.text().then(f)}),this.json=function(){return this.text().then(JSON.parse)},this}function c(e){var t=e.toUpperCase()
|
||||||
|
|
||||||
|
|
||||||
return m.indexOf(t)>-1?t:e}function c(e,t){t=t||{}
|
return g.indexOf(t)>-1?t:e}function d(e,t){t=t||{}
|
||||||
var n=t.body
|
var n=t.body
|
||||||
if(c.prototype.isPrototypeOf(e)){if(e.bodyUsed)throw new TypeError("Already read")
|
if(d.prototype.isPrototypeOf(e)){if(e.bodyUsed)throw new TypeError("Already read")
|
||||||
this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new i(e.headers)),this.method=e.method,this.mode=e.mode,n||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=e
|
this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new r(e.headers)),this.method=e.method,this.mode=e.mode,n||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=e
|
||||||
if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new i(t.headers)),this.method=u(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,
|
if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new r(t.headers)),this.method=c(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,
|
||||||
("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests")
|
("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests")
|
||||||
this._initBody(n)}function d(e){var t=new FormData
|
this._initBody(n)}function f(e){var t=new FormData
|
||||||
return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),i=n.shift().replace(/\+/g," "),r=n.join("=").replace(/\+/g," ")
|
return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),i=n.shift().replace(/\+/g," "),r=n.join("=").replace(/\+/g," ")
|
||||||
t.append(decodeURIComponent(i),decodeURIComponent(r))}}),t}function f(e){var t=new i,n=e.getAllResponseHeaders().trim().split("\n")
|
t.append(decodeURIComponent(i),decodeURIComponent(r))}}),t}function p(e){var t=new r,n=(e.getAllResponseHeaders()||"").trim().split("\n")
|
||||||
return n.forEach(function(e){var n=e.trim().split(":"),i=n.shift().trim(),r=n.join(":").trim()
|
return n.forEach(function(e){var n=e.trim().split(":"),i=n.shift().trim(),r=n.join(":").trim()
|
||||||
t.append(i,r)}),t}function p(e,t){t||(t={}),this.type="default",this.status=t.status,this.ok=this.status>=200&&this.status<300,this.statusText=t.statusText,this.headers=t.headers instanceof i?t.headers:new i(t.headers),
|
t.append(i,r)}),t}function h(e,t){t||(t={}),this.type="default",this.status=t.status,this.ok=this.status>=200&&this.status<300,this.statusText=t.statusText,this.headers=t.headers instanceof r?t.headers:new r(t.headers),
|
||||||
this.url=t.url||"",this._initBody(e)}if(!e.fetch){i.prototype.append=function(e,i){e=t(e),i=n(i)
|
this.url=t.url||"",this._initBody(e)}if(!e.fetch){var m={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,
|
||||||
|
!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e}
|
||||||
|
r.prototype.append=function(e,i){e=t(e),i=n(i)
|
||||||
var r=this.map[e]
|
var r=this.map[e]
|
||||||
r||(r=[],this.map[e]=r),r.push(i)},i.prototype["delete"]=function(e){delete this.map[t(e)]},i.prototype.get=function(e){var n=this.map[t(e)]
|
r||(r=[],this.map[e]=r),r.push(i)},r.prototype["delete"]=function(e){delete this.map[t(e)]},r.prototype.get=function(e){var n=this.map[t(e)]
|
||||||
return n?n[0]:null},i.prototype.getAll=function(e){return this.map[t(e)]||[]},i.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},i.prototype.set=function(e,i){this.map[t(e)]=[n(i)]},i.prototype.forEach=function(e,t){
|
return n?n[0]:null},r.prototype.getAll=function(e){return this.map[t(e)]||[]},r.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},r.prototype.set=function(e,i){this.map[t(e)]=[n(i)]},r.prototype.forEach=function(e,t){
|
||||||
Object.getOwnPropertyNames(this.map).forEach(function(n){this.map[n].forEach(function(i){e.call(t,i,n,this)},this)},this)}
|
Object.getOwnPropertyNames(this.map).forEach(function(n){this.map[n].forEach(function(i){e.call(t,i,n,this)},this)},this)},r.prototype.keys=function(){var e=[]
|
||||||
var h={blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e},m=["DELETE","GET","HEAD","OPTIONS","POST","PUT"]
|
return this.forEach(function(t,n){e.push(n)}),i(e)},r.prototype.values=function(){var e=[]
|
||||||
|
return this.forEach(function(t){e.push(t)}),i(e)},r.prototype.entries=function(){var e=[]
|
||||||
|
return this.forEach(function(t,n){e.push([n,t])}),i(e)},m.iterable&&(r.prototype[Symbol.iterator]=r.prototype.entries)
|
||||||
c.prototype.clone=function(){return new c(this)},l.call(c.prototype),l.call(p.prototype),p.prototype.clone=function(){return new p(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new i(this.headers),
|
var g=["DELETE","GET","HEAD","OPTIONS","POST","PUT"]
|
||||||
url:this.url})},p.error=function(){var e=new p(null,{status:0,statusText:""})
|
d.prototype.clone=function(){return new d(this)},u.call(d.prototype),u.call(h.prototype),h.prototype.clone=function(){return new h(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new r(this.headers),
|
||||||
|
url:this.url})},h.error=function(){var e=new h(null,{status:0,statusText:""})
|
||||||
return e.type="error",e}
|
return e.type="error",e}
|
||||||
var g=[301,302,303,307,308]
|
var v=[301,302,303,307,308]
|
||||||
p.redirect=function(e,t){if(g.indexOf(t)===-1)throw new RangeError("Invalid status code")
|
h.redirect=function(e,t){if(v.indexOf(t)===-1)throw new RangeError("Invalid status code")
|
||||||
return new p(null,{status:t,headers:{location:e}})},e.Headers=i,e.Request=c,e.Response=p,e.fetch=function(e,t){return new Promise(function(n,i){function r(){return"responseURL"in a?a.responseURL:/^X-Request-URL:/m.test(a.getAllResponseHeaders())?a.getResponseHeader("X-Request-URL"):void 0
|
return new h(null,{status:t,headers:{location:e}})},e.Headers=r,e.Request=d,e.Response=h,e.fetch=function(e,t){return new Promise(function(n,i){function r(){return"responseURL"in a?a.responseURL:/^X-Request-URL:/m.test(a.getAllResponseHeaders())?a.getResponseHeader("X-Request-URL"):void 0
|
||||||
|
|
||||||
}var o
|
}var o
|
||||||
o=c.prototype.isPrototypeOf(e)&&!t?e:new c(e,t)
|
o=d.prototype.isPrototypeOf(e)&&!t?e:new d(e,t)
|
||||||
var a=new XMLHttpRequest
|
var a=new XMLHttpRequest
|
||||||
a.onload=function(){var e=1223===a.status?204:a.status
|
a.onload=function(){var e={status:a.status,statusText:a.statusText,headers:p(a),url:r()},t="response"in a?a.response:a.responseText
|
||||||
if(e<100||e>599)return void i(new TypeError("Network request failed"))
|
n(new h(t,e))},a.onerror=function(){i(new TypeError("Network request failed"))},a.ontimeout=function(){i(new TypeError("Network request failed"))},a.open(o.method,o.url,!0),"include"===o.credentials&&(a.withCredentials=!0),
|
||||||
var t={status:e,statusText:a.statusText,headers:f(a),url:r()},o="response"in a?a.response:a.responseText
|
"responseType"in a&&m.blob&&(a.responseType="blob"),o.headers.forEach(function(e,t){a.setRequestHeader(t,e)}),a.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)
|
||||||
n(new p(o,t))},a.onerror=function(){i(new TypeError("Network request failed"))},a.open(o.method,o.url,!0),"include"===o.credentials&&(a.withCredentials=!0),"responseType"in a&&h.blob&&(a.responseType="blob"),
|
|
||||||
o.headers.forEach(function(e,t){a.setRequestHeader(t,e)}),a.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(e,t,n){var i;(function(t,r){
|
},function(e,t,n){var i;(function(t,r){!function(t,n){e.exports=n()}(this,function(){"use strict"
|
||||||
!function(t,n){e.exports=n()}(this,function(){"use strict"
|
|
||||||
function e(e){return"function"==typeof e||"object"==typeof e&&null!==e}function o(e){return"function"==typeof e}function a(e){K=e}function s(e){Y=e}function l(){return function(){return t.nextTick(p)}}
|
function e(e){return"function"==typeof e||"object"==typeof e&&null!==e}function o(e){return"function"==typeof e}function a(e){K=e}function s(e){Y=e}function l(){return function(){return t.nextTick(p)}}
|
||||||
function u(){return function(){Q(p)}}function c(){var e=0,t=new ee(p),n=document.createTextNode("")
|
function u(){return function(){Q(p)}}function c(){var e=0,t=new ee(p),n=document.createTextNode("")
|
||||||
return t.observe(n,{characterData:!0}),function(){n.data=e=++e%2}}function d(){var e=new MessageChannel
|
return t.observe(n,{characterData:!0}),function(){n.data=e=++e%2}}function d(){var e=new MessageChannel
|
||||||
@ -1824,8 +1829,8 @@ e.save(),(0,_jQuery2["default"])(e.getElement()).trigger("change")},create:funct
|
|||||||
},selectNode:function h(e){this.getInstance().selection.select(e)},setContent:function m(e,t){this.getInstance().setContent(e,t)},insertContent:function g(e,t){this.getInstance().insertContent(e,t)},replaceContent:function v(e,t){
|
},selectNode:function h(e){this.getInstance().selection.select(e)},setContent:function m(e,t){this.getInstance().setContent(e,t)},insertContent:function g(e,t){this.getInstance().insertContent(e,t)},replaceContent:function v(e,t){
|
||||||
this.getInstance().execCommand("mceReplaceContent",!1,e,t)},insertLink:function y(e,t){this.getInstance().execCommand("mceInsertLink",!1,e,t)},removeLink:function b(){this.getInstance().execCommand("unlink",!1)
|
this.getInstance().execCommand("mceReplaceContent",!1,e,t)},insertLink:function y(e,t){this.getInstance().execCommand("mceInsertLink",!1,e,t)},removeLink:function b(){this.getInstance().execCommand("unlink",!1)
|
||||||
|
|
||||||
},cleanLink:function cleanLink(href,node){var settings=this.getConfig,cb=settings.urlconverter_callback
|
},cleanLink:function cleanLink(href,node){var settings=this.getConfig,cb=settings.urlconverter_callback,cu=tinyMCE.settings.convert_urls
|
||||||
return cb&&(href=eval(cb+"(href, node, true);")),href.match(new RegExp("^"+tinyMCE.settings.document_base_url+"(.*)$"))&&(href=RegExp.$1),href.match(/^javascript:\s*mctmp/)&&(href=""),href},createBookmark:function w(){
|
return cb&&(href=eval(cb+"(href, node, true);")),cu&&href.match(new RegExp("^"+tinyMCE.settings.document_base_url+"(.*)$"))&&(href=RegExp.$1),href.match(/^javascript:\s*mctmp/)&&(href=""),href},createBookmark:function w(){
|
||||||
return this.getInstance().selection.getBookmark()},moveToBookmark:function _(e){this.getInstance().selection.moveToBookmark(e),this.getInstance().focus()},blur:function C(){this.getInstance().selection.collapse()
|
return this.getInstance().selection.getBookmark()},moveToBookmark:function _(e){this.getInstance().selection.moveToBookmark(e),this.getInstance().focus()},blur:function C(){this.getInstance().selection.collapse()
|
||||||
|
|
||||||
},addUndo:function T(){this.getInstance().undoManager.add()}}},ss.editorWrappers["default"]=ss.editorWrappers.tinyMCE,_jQuery2["default"].entwine("ss",function(e){e("textarea.htmleditor").entwine({Editor:null,
|
},addUndo:function T(){this.getInstance().undoManager.add()}}},ss.editorWrappers["default"]=ss.editorWrappers.tinyMCE,_jQuery2["default"].entwine("ss",function(e){e("textarea.htmleditor").entwine({Editor:null,
|
||||||
|
2
admin/client/dist/js/vendor.js
vendored
2
admin/client/dist/js/vendor.js
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,4 @@
|
|||||||
import jQuery from 'jQuery';
|
import jQuery from 'jQuery';
|
||||||
import i18n from 'i18n';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
@ -216,11 +216,12 @@ ss.editorWrappers.tinyMCE = (function() {
|
|||||||
*/
|
*/
|
||||||
cleanLink: function(href, node) {
|
cleanLink: function(href, node) {
|
||||||
var settings = this.getConfig,
|
var settings = this.getConfig,
|
||||||
cb = settings['urlconverter_callback'];
|
cb = settings['urlconverter_callback'],
|
||||||
|
cu = tinyMCE.settings['convert_urls'];
|
||||||
if(cb) href = eval(cb + "(href, node, true);");
|
if(cb) href = eval(cb + "(href, node, true);");
|
||||||
|
|
||||||
// Turn into relative
|
// Turn into relative, if set in TinyMCE config
|
||||||
if(href.match(new RegExp('^' + tinyMCE.settings['document_base_url'] + '(.*)$'))) {
|
if(cu && href.match(new RegExp('^' + tinyMCE.settings['document_base_url'] + '(.*)$'))) {
|
||||||
href = RegExp.$1;
|
href = RegExp.$1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ abstract class ModelAdmin extends LeftAndMain {
|
|||||||
|
|
||||||
// Parse all DateFields to handle user input non ISO 8601 dates
|
// Parse all DateFields to handle user input non ISO 8601 dates
|
||||||
foreach($context->getFields() as $field) {
|
foreach($context->getFields() as $field) {
|
||||||
if($field instanceof DatetimeField) {
|
if($field instanceof DatetimeField && !empty($params[$field->getName()])) {
|
||||||
$params[$field->getName()] = date('Y-m-d', strtotime($params[$field->getName()]));
|
$params[$field->getName()] = date('Y-m-d', strtotime($params[$field->getName()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,7 +435,7 @@ When in a particular scope, `$Up` takes the scope back to the previous level.
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
Given the following structure, it will output the text.
|
Given the following structure, it will output the text.
|
||||||
|
|
||||||
My Page
|
My Page
|
||||||
|
|
|
|
||||||
+-+ Child 1
|
+-+ Child 1
|
||||||
@ -450,6 +450,16 @@ Given the following structure, it will output the text.
|
|||||||
Page 'Grandchild 1' is a grandchild of 'My Page'
|
Page 'Grandchild 1' is a grandchild of 'My Page'
|
||||||
Page 'Child 2' is a child of 'MyPage'
|
Page 'Child 2' is a child of 'MyPage'
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
Additional selectors implicitely change the scope so you need to put additional `$Up` to get what you expect.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::ss
|
||||||
|
<h1>Children of '$Title'</h1>
|
||||||
|
<% loop $Children.Sort('Title').First %>
|
||||||
|
<%-- We have two additional selectors in the loop expression so... --%>
|
||||||
|
<p>Page '$Title' is a child of '$Up.Up.Up.Title'</p>
|
||||||
|
<% end_loop %>
|
||||||
|
|
||||||
#### Top
|
#### Top
|
||||||
|
|
||||||
@ -467,8 +477,6 @@ page. The previous example could be rewritten to use the following syntax.
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### With
|
### With
|
||||||
|
|
||||||
The `<% with %>` tag lets you change into a new scope. Consider the following example:
|
The `<% with %>` tag lets you change into a new scope. Consider the following example:
|
||||||
@ -489,7 +497,12 @@ Outside the `<% with %>.`, we are in the page scope. Inside it, we are in the sc
|
|||||||
refer directly to properties and methods of the [api:Member] object. `$FirstName` inside the scope is equivalent to
|
refer directly to properties and methods of the [api:Member] object. `$FirstName` inside the scope is equivalent to
|
||||||
`$CurrentMember.FirstName`.
|
`$CurrentMember.FirstName`.
|
||||||
|
|
||||||
|
### Me
|
||||||
|
|
||||||
|
`$Me` outputs the current object in scope. This will call the `forTemplate` of the object.
|
||||||
|
|
||||||
|
:::ss
|
||||||
|
$Me
|
||||||
|
|
||||||
## Comments
|
## Comments
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ exhaustive list. From your template you can call any method, database field, or
|
|||||||
currently in scope as well as its' subclasses or extensions.
|
currently in scope as well as its' subclasses or extensions.
|
||||||
|
|
||||||
Knowing what methods you can call can be tricky, but the first step is to understand the scope you're in. Scope is
|
Knowing what methods you can call can be tricky, but the first step is to understand the scope you're in. Scope is
|
||||||
explained in more detail on the [syntax](syntax#scope) page.
|
explained in more detail on the [syntax](syntax#scope) page. Many of the methods listed below can be called from any
|
||||||
|
scope, and you can specify additional static methods to be available globally in templates by implementing the
|
||||||
|
[api:TemplateGlobalProvider] interface.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
<div class="notice" markdown="1">
|
||||||
Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string
|
Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string
|
||||||
@ -302,62 +304,7 @@ For example, imagine you're on the "bob marley" page, which is three levels in:
|
|||||||
|
|
||||||
## Navigating Scope
|
## Navigating Scope
|
||||||
|
|
||||||
### Me
|
See [scope](syntax#scope).
|
||||||
|
|
||||||
`$Me` outputs the current object in scope. This will call the `forTemplate` of the object.
|
|
||||||
|
|
||||||
:::ss
|
|
||||||
$Me
|
|
||||||
|
|
||||||
|
|
||||||
### Up
|
|
||||||
|
|
||||||
When in a particular scope, `$Up` takes the scope back to the previous level.
|
|
||||||
|
|
||||||
:::ss
|
|
||||||
<h1>Children of '$Title'</h1>
|
|
||||||
|
|
||||||
<% loop $Children %>
|
|
||||||
<p>Page '$Title' is a child of '$Up.Title'</p>
|
|
||||||
|
|
||||||
<% loop $Children %>
|
|
||||||
<p>Page '$Title' is a grandchild of '$Up.Up.Title'</p>
|
|
||||||
<% end_loop %>
|
|
||||||
<% end_loop %>
|
|
||||||
|
|
||||||
Given the following structure, it will output the text.
|
|
||||||
|
|
||||||
My Page
|
|
||||||
|
|
|
||||||
+-+ Child 1
|
|
||||||
| |
|
|
||||||
| +- Grandchild 1
|
|
||||||
|
|
|
||||||
+-+ Child 2
|
|
||||||
|
|
||||||
Children of 'My Page'
|
|
||||||
|
|
||||||
Page 'Child 1' is a child of 'My Page'
|
|
||||||
Page 'Grandchild 1' is a grandchild of 'My Page'
|
|
||||||
Page 'Child 2' is a child of 'MyPage'
|
|
||||||
|
|
||||||
|
|
||||||
### Top
|
|
||||||
|
|
||||||
While `$Up` provides us a way to go up one level of scope, `$Top` is a shortcut to jump to the top most scope of the
|
|
||||||
page. The previous example could be rewritten to use the following syntax.
|
|
||||||
|
|
||||||
:::ss
|
|
||||||
<h1>Children of '$Title'</h1>
|
|
||||||
|
|
||||||
<% loop $Children %>
|
|
||||||
<p>Page '$Title' is a child of '$Top.Title'</p>
|
|
||||||
|
|
||||||
<% loop $Children %>
|
|
||||||
<p>Page '$Title' is a grandchild of '$Top.Title'</p>
|
|
||||||
<% end_loop %>
|
|
||||||
<% end_loop %>
|
|
||||||
|
|
||||||
|
|
||||||
## Breadcrumbs
|
## Breadcrumbs
|
||||||
|
|
||||||
|
@ -10,6 +10,11 @@ or even their own code to make it more reusable.
|
|||||||
Extensions are defined as subclasses of either [api:DataExtension] for extending a [api:DataObject] subclass or
|
Extensions are defined as subclasses of either [api:DataExtension] for extending a [api:DataObject] subclass or
|
||||||
the [api:Extension] class for non DataObject subclasses (such as [api:Controllers])
|
the [api:Extension] class for non DataObject subclasses (such as [api:Controllers])
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
For performance reasons a few classes are excluded from receiving extensions, including `Object`, `ViewableData`
|
||||||
|
and `RequestHandler`. You can still apply extensions to descendants of these classes.
|
||||||
|
</div>
|
||||||
|
|
||||||
**mysite/code/extensions/MyMemberExtension.php**
|
**mysite/code/extensions/MyMemberExtension.php**
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
title: Template debugging
|
||||||
|
summary: Track down which template rendered a piece of html
|
||||||
|
|
||||||
|
# Debugging templates
|
||||||
|
|
||||||
|
## Source code comments
|
||||||
|
|
||||||
|
If there is a problem with the rendered html your page is outputting you may need
|
||||||
|
to track down a template or two. The template engine can help you along by displaying
|
||||||
|
source code comments indicating which template is responsible for rendering each
|
||||||
|
block of html on your page.
|
||||||
|
|
||||||
|
::::yaml
|
||||||
|
---
|
||||||
|
Only:
|
||||||
|
environment: 'dev'
|
||||||
|
---
|
||||||
|
SSViewer:
|
||||||
|
source_file_comments: true
|
@ -74,34 +74,36 @@ Note the use of both `.max('LastEdited')` and `.count()` - this takes care of bo
|
|||||||
edited since the cache was last built, and also when an object has been deleted since the cache was last built.
|
edited since the cache was last built, and also when an object has been deleted since the cache was last built.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
We can also calculate aggregates on relationships. A block that shows the current member's favorites needs to update
|
We can also calculate aggregates on relationships. The logic for that can get a bit complex, so we can extract that on
|
||||||
whenever the relationship `Member::$has_many = array('Favourites' => Favourite')` changes.
|
to the controller so it's not cluttering up our template.
|
||||||
|
|
||||||
:::ss
|
|
||||||
<% cached 'favourites', $CurrentMember.ID, $CurrentMember.Favourites.max('LastEdited') %>
|
|
||||||
|
|
||||||
## Cache key calculated in controller
|
## Cache key calculated in controller
|
||||||
|
|
||||||
In the previous example the cache key is getting a bit large, and is complicating our template up. Better would be to
|
If your caching logic is complex or re-usable, you can define a method on your controller to generate a cache key
|
||||||
extract that logic into the controller.
|
fragment.
|
||||||
|
|
||||||
|
For example, a block that shows a collection of rotating slides needs to update whenever the relationship
|
||||||
|
`Page::$many_many = array('Slides' => 'Slide')` changes. In Page_Controller:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
|
|
||||||
public function FavouriteCacheKey() {
|
public function SliderCacheKey() {
|
||||||
$member = Member::currentUser();
|
$fragments = array(
|
||||||
|
'Page-Slides',
|
||||||
return implode('_', array(
|
$this->ID,
|
||||||
'favourites',
|
// identify which objects are in the list and their sort order
|
||||||
$member->ID,
|
implode('-', $this->Slides()->Column('ID')),
|
||||||
$member->Favourites()->max('LastEdited')
|
$this->Slides()->max('LastEdited')
|
||||||
));
|
);
|
||||||
|
return implode('-_-', $fragments);
|
||||||
}
|
}
|
||||||
|
|
||||||
Then using that function in the cache key:
|
Then reference that function in the cache key:
|
||||||
|
|
||||||
:::ss
|
:::ss
|
||||||
<% cached $FavouriteCacheKey %>
|
<% cached $SliderCacheKey %>
|
||||||
|
|
||||||
|
The example above would work for both a has_many and many_many relationship.
|
||||||
|
|
||||||
## Cache blocks and template changes
|
## Cache blocks and template changes
|
||||||
|
|
||||||
@ -207,8 +209,8 @@ could also write the last example as:
|
|||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
<div class="warning" markdown="1">
|
||||||
Currently cached blocks can not be contained within if or loop blocks. The template engine will throw an error
|
Currently a nested cache block can not be contained within an if or loop block. The template engine will throw an error
|
||||||
letting you know if you've done this. You can often get around this using aggregates.
|
letting you know if you've done this. You can often get around this using aggregates or by un-nesting the block.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Failing example:
|
Failing example:
|
||||||
@ -217,7 +219,7 @@ Failing example:
|
|||||||
<% cached $LastEdited %>
|
<% cached $LastEdited %>
|
||||||
|
|
||||||
<% loop $Children %>
|
<% loop $Children %>
|
||||||
<% cached LastEdited %>
|
<% cached $LastEdited %>
|
||||||
$Name
|
$Name
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
@ -236,3 +238,29 @@ Can be re-written as:
|
|||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
:::ss
|
||||||
|
<% cached $LastEdited %>
|
||||||
|
(other code)
|
||||||
|
<% end_cached %>
|
||||||
|
|
||||||
|
<% loop $Children %>
|
||||||
|
<% cached $LastEdited %>
|
||||||
|
$Name
|
||||||
|
<% end_cached %>
|
||||||
|
<% end_loop %>
|
||||||
|
|
||||||
|
## Cache expiry
|
||||||
|
|
||||||
|
The default expiry for partial caches is 10 minutes. The advantage of a short cache expiry is that if you have a problem
|
||||||
|
with your caching logic, the window in which stale content may be shown is short. The disadvantage, particularly for
|
||||||
|
low-traffic sites, is that cache blocks may expire before they can be utilised. If you're confident that you're caching
|
||||||
|
logic is sound, you could increase the expiry dramatically.
|
||||||
|
|
||||||
|
**mysite/_config.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
// Set partial cache expiry to 7 days
|
||||||
|
SS_Cache::set_cache_lifetime('cacheblock', 60 * 60 * 24 * 7);
|
||||||
|
@ -9,7 +9,7 @@ covers how to create an `Email` instance, customise it with a HTML template, the
|
|||||||
|
|
||||||
Out of the box, SilverStripe will use the built-in PHP `mail()` command. If you are not running an SMTP server, you
|
Out of the box, SilverStripe will use the built-in PHP `mail()` command. If you are not running an SMTP server, you
|
||||||
will need to either configure PHP's SMTP settings (see [PHP documentation](http://php.net/mail) to include your mail
|
will need to either configure PHP's SMTP settings (see [PHP documentation](http://php.net/mail) to include your mail
|
||||||
server configuration or use one of the third party SMTP services like [Mandrill](https://github.com/lekoala/silverstripe-mandrill)
|
server configuration or use one of the third party SMTP services like [SparkPost](https://github.com/lekoala/silverstripe-sparkpost)
|
||||||
and [Postmark](https://github.com/fullscreeninteractive/silverstripe-postmarkmailer).
|
and [Postmark](https://github.com/fullscreeninteractive/silverstripe-postmarkmailer).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
3
main.php
3
main.php
@ -77,6 +77,9 @@ if(!empty($_SERVER['HTTP_X_ORIGINAL_URL'])) {
|
|||||||
$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
|
$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable the entity loader to be able to load XML in Zend_Locale_Data
|
||||||
|
libxml_disable_entity_loader(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Figure out the request URL
|
* Figure out the request URL
|
||||||
*/
|
*/
|
||||||
|
@ -250,6 +250,16 @@ class Session {
|
|||||||
$session_path = Config::inst()->get('SilverStripe\\Control\\Session', 'session_store_path');
|
$session_path = Config::inst()->get('SilverStripe\\Control\\Session', 'session_store_path');
|
||||||
$timeout = Config::inst()->get('SilverStripe\\Control\\Session', 'timeout');
|
$timeout = Config::inst()->get('SilverStripe\\Control\\Session', 'timeout');
|
||||||
|
|
||||||
|
// Director::baseURL can return absolute domain names - this extracts the relevant parts
|
||||||
|
// for the session otherwise we can get broken session cookies
|
||||||
|
if (Director::is_absolute_url($path)) {
|
||||||
|
$urlParts = parse_url($path);
|
||||||
|
$path = $urlParts['path'];
|
||||||
|
if (!$domain) {
|
||||||
|
$domain = $urlParts['host'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!session_id() && !headers_sent()) {
|
if(!session_id() && !headers_sent()) {
|
||||||
if($domain) {
|
if($domain) {
|
||||||
session_set_cookie_params($timeout, $path, $domain, $secure, true);
|
session_set_cookie_params($timeout, $path, $domain, $secure, true);
|
||||||
|
@ -593,7 +593,12 @@ class FieldList extends ArrayList {
|
|||||||
public function makeFieldReadonly($field) {
|
public function makeFieldReadonly($field) {
|
||||||
$fieldName = ($field instanceof FormField) ? $field->getName() : $field;
|
$fieldName = ($field instanceof FormField) ? $field->getName() : $field;
|
||||||
$srcField = $this->dataFieldByName($fieldName);
|
$srcField = $this->dataFieldByName($fieldName);
|
||||||
$this->replaceField($fieldName, $srcField->performReadonlyTransformation());
|
if($srcField) {
|
||||||
|
$this->replaceField($fieldName, $srcField->performReadonlyTransformation());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
user_error("Trying to make field '$fieldName' readonly, but it does not exist in the list",E_USER_WARNING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -455,7 +455,7 @@ class ArrayList extends ViewableData implements SS_List, Filterable, Sortable, L
|
|||||||
// First argument is the direction to be sorted,
|
// First argument is the direction to be sorted,
|
||||||
$multisortArgs[] = &$sortDirection[$column];
|
$multisortArgs[] = &$sortDirection[$column];
|
||||||
if ($firstRun) {
|
if ($firstRun) {
|
||||||
$multisortArgs[] = defined('SORT_NATURAL') ? SORT_NATURAL : SORT_STRING;
|
$multisortArgs[] = SORT_REGULAR;
|
||||||
}
|
}
|
||||||
$firstRun = false;
|
$firstRun = false;
|
||||||
}
|
}
|
||||||
|
@ -480,6 +480,22 @@ class MySQLSchemaManager extends DBSchemaManager {
|
|||||||
return "int(11) not null" . $this->defaultClause($values);
|
return "int(11) not null" . $this->defaultClause($values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a bigint type-formatted string
|
||||||
|
*
|
||||||
|
* @param array $values Contains a tokenised list of info about this data type
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function bigint($values) {
|
||||||
|
//For reference, this is what typically gets passed to this function:
|
||||||
|
//$parts=Array('datatype'=>'bigint', 'precision'=>20, 'null'=>'not null', 'default'=>$this->defaultVal,
|
||||||
|
// 'arrayValue'=>$this->arrayValue);
|
||||||
|
//$values=Array('type'=>'bigint', 'parts'=>$parts);
|
||||||
|
//DB::requireField($this->tableName, $this->name, $values);
|
||||||
|
|
||||||
|
return 'bigint(20) not null' . $this->defaultClause($values);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a datetime type-formatted string
|
* Return a datetime type-formatted string
|
||||||
* For MySQL, we simply return the word 'datetime', no other parameters are necessary
|
* For MySQL, we simply return the word 'datetime', no other parameters are necessary
|
||||||
|
@ -444,7 +444,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
|||||||
} elseif($numberFuncArgs == 2) {
|
} elseif($numberFuncArgs == 2) {
|
||||||
$whereArguments[func_get_arg(0)] = func_get_arg(1);
|
$whereArguments[func_get_arg(0)] = func_get_arg(1);
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentException('Incorrect number of arguments passed to exclude()');
|
throw new InvalidArgumentException('Incorrect number of arguments passed to filterAny()');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->alterDataQuery(function(DataQuery $query) use ($whereArguments) {
|
return $this->alterDataQuery(function(DataQuery $query) use ($whereArguments) {
|
||||||
|
@ -3062,7 +3062,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* @uses DataExtension->requireDefaultRecords()
|
* @uses DataExtension->requireDefaultRecords()
|
||||||
*/
|
*/
|
||||||
public function requireDefaultRecords() {
|
public function requireDefaultRecords() {
|
||||||
$defaultRecords = $this->stat('default_records');
|
$defaultRecords = $this->config()->get('default_records', Config::UNINHERITED);
|
||||||
|
|
||||||
if(!empty($defaultRecords)) {
|
if(!empty($defaultRecords)) {
|
||||||
$hasData = DataObject::get_one($this->class);
|
$hasData = DataObject::get_one($this->class);
|
||||||
|
@ -436,7 +436,8 @@ class DataQuery {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function max($field) {
|
public function max($field) {
|
||||||
return $this->aggregate("MAX(\"$field\")");
|
$table = ClassInfo::table_for_object_field($this->dataClass, $field);
|
||||||
|
return $this->aggregate("MAX(\"$table\".\"$field\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -447,7 +448,8 @@ class DataQuery {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function min($field) {
|
public function min($field) {
|
||||||
return $this->aggregate("MIN(\"$field\")");
|
$table = ClassInfo::table_for_object_field($this->dataClass, $field);
|
||||||
|
return $this->aggregate("MIN(\"$table\".\"$field\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -458,7 +460,8 @@ class DataQuery {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function avg($field) {
|
public function avg($field) {
|
||||||
return $this->aggregate("AVG(\"$field\")");
|
$table = ClassInfo::table_for_object_field($this->dataClass, $field);
|
||||||
|
return $this->aggregate("AVG(\"$table\".\"$field\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -469,7 +472,8 @@ class DataQuery {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function sum($field) {
|
public function sum($field) {
|
||||||
return $this->aggregate("SUM(\"$field\")");
|
$table = ClassInfo::table_for_object_field($this->dataClass, $field);
|
||||||
|
return $this->aggregate("SUM(\"$table\".\"$field\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
29
src/ORM/FieldType/DBBigint.php
Normal file
29
src/ORM/FieldType/DBBigint.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\ORM\FieldType;
|
||||||
|
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a signed 8 byte integer field. Do note PHP running as 32-bit might not work with Bigint properly, as it
|
||||||
|
* would convert the value to a float when queried from the database since the value is a 64-bit one.
|
||||||
|
*
|
||||||
|
* @package framework
|
||||||
|
* @subpackage model
|
||||||
|
* @see Int
|
||||||
|
*/
|
||||||
|
class DBBigInt extends DBInt {
|
||||||
|
|
||||||
|
public function requireField() {
|
||||||
|
$parts = array(
|
||||||
|
'datatype' => 'bigint',
|
||||||
|
'precision' => 8,
|
||||||
|
'null' => 'not null',
|
||||||
|
'default' => $this->defaultVal,
|
||||||
|
'arrayValue' => $this->arrayValue
|
||||||
|
);
|
||||||
|
|
||||||
|
$values = array('type' => 'bigint', 'parts' => $parts);
|
||||||
|
DB::require_field($this->tableName, $this->name, $values);
|
||||||
|
}
|
||||||
|
}
|
@ -10,22 +10,21 @@ use SilverStripe\Core\Object;
|
|||||||
*/
|
*/
|
||||||
class ValidationResult extends Object {
|
class ValidationResult extends Object {
|
||||||
/**
|
/**
|
||||||
* Boolean - is the result valid or not
|
* @var bool - is the result valid or not
|
||||||
*/
|
*/
|
||||||
protected $isValid;
|
protected $isValid;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of errors
|
* @var array of errors
|
||||||
*/
|
*/
|
||||||
protected $errorList = array();
|
protected $errorList = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new ValidationResult.
|
* Create a new ValidationResult.
|
||||||
* By default, it is a successful result. Call $this->error() to record errors.
|
* By default, it is a successful result. Call $this->error() to record errors.
|
||||||
*
|
|
||||||
* @param bool $valid
|
* @param bool $valid
|
||||||
* @param string $message
|
* @param string|null $message
|
||||||
*/
|
*/
|
||||||
public function __construct($valid = true, $message = null) {
|
public function __construct($valid = true, $message = null) {
|
||||||
$this->isValid = $valid;
|
$this->isValid = $valid;
|
||||||
@ -36,7 +35,7 @@ class ValidationResult extends Object {
|
|||||||
/**
|
/**
|
||||||
* Record an error against this validation result,
|
* Record an error against this validation result,
|
||||||
* @param string $message The validation error message
|
* @param string $message The validation error message
|
||||||
* @param int $code An optional error code string, that can be accessed with {@link $this->codeList()}.
|
* @param string $code An optional error code string, that can be accessed with {@link $this->codeList()}.
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function error($message, $code = null) {
|
public function error($message, $code = null) {
|
||||||
|
@ -119,6 +119,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
|
* @config
|
||||||
*/
|
*/
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Version' => 'Int'
|
'Version' => 'Int'
|
||||||
@ -1426,7 +1427,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the current draft version is the same as live
|
* Determines if the current draft version is the same as live or rather, that there are no outstanding draft changes
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
@ -28,6 +28,13 @@ class ShortcodeParser extends Object {
|
|||||||
|
|
||||||
// --------------------------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered shortcodes. Items follow this structure:
|
||||||
|
* [shortcode_name] => Array(
|
||||||
|
* [0] => class_containing_handler
|
||||||
|
* [1] => name_of_shortcode_handler_method
|
||||||
|
* )
|
||||||
|
*/
|
||||||
protected $shortcodes = array();
|
protected $shortcodes = array();
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------------------------
|
||||||
@ -109,6 +116,15 @@ class ShortcodeParser extends Object {
|
|||||||
if($this->registered($shortcode)) unset($this->shortcodes[$shortcode]);
|
if($this->registered($shortcode)) unset($this->shortcodes[$shortcode]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array containing information about registered shortcodes
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRegisteredShortcodes() {
|
||||||
|
return $this->shortcodes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all registered shortcodes.
|
* Remove all registered shortcodes.
|
||||||
*/
|
*/
|
||||||
@ -586,81 +602,92 @@ class ShortcodeParser extends Object {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function parse($content) {
|
public function parse($content) {
|
||||||
|
|
||||||
|
$this->extend('onBeforeParse', $content);
|
||||||
|
|
||||||
|
$continue = true;
|
||||||
|
|
||||||
// If no shortcodes defined, don't try and parse any
|
// If no shortcodes defined, don't try and parse any
|
||||||
if(!$this->shortcodes) return $content;
|
if(!$this->shortcodes) $continue = false;
|
||||||
|
|
||||||
// If no content, don't try and parse it
|
// If no content, don't try and parse it
|
||||||
if (!trim($content)) return $content;
|
else if (!trim($content)) $continue = false;
|
||||||
|
|
||||||
// If no shortcode tag, don't try and parse it
|
// If no shortcode tag, don't try and parse it
|
||||||
if (strpos($content, '[') === false) return $content;
|
else if (strpos($content, '[') === false) $continue = false;
|
||||||
|
|
||||||
// First we operate in text mode, replacing any shortcodes with marker elements so that later we can
|
if ($continue) {
|
||||||
// use a proper DOM
|
// First we operate in text mode, replacing any shortcodes with marker elements so that later we can
|
||||||
list($content, $tags) = $this->replaceElementTagsWithMarkers($content);
|
// use a proper DOM
|
||||||
|
list($content, $tags) = $this->replaceElementTagsWithMarkers($content);
|
||||||
|
|
||||||
/** @var HTMLValue $htmlvalue */
|
/** @var HTMLValue $htmlvalue */
|
||||||
$htmlvalue = Injector::inst()->create('HTMLValue', $content);
|
$htmlvalue = Injector::inst()->create('HTMLValue', $content);
|
||||||
|
|
||||||
// Now parse the result into a DOM
|
// Now parse the result into a DOM
|
||||||
if (!$htmlvalue->isValid()){
|
if (!$htmlvalue->isValid()){
|
||||||
if(self::$error_behavior == self::ERROR) {
|
if(self::$error_behavior == self::ERROR) {
|
||||||
user_error('Couldn\'t decode HTML when processing short codes', E_USER_ERROR);
|
user_error('Couldn\'t decode HTML when processing short codes', E_USER_ERRROR);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return $content;
|
$continue = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, replace any shortcodes that are in attributes
|
if ($continue) {
|
||||||
$this->replaceAttributeTagsWithContent($htmlvalue);
|
// First, replace any shortcodes that are in attributes
|
||||||
|
$this->replaceAttributeTagsWithContent($htmlvalue);
|
||||||
|
|
||||||
// Find all the element scoped shortcode markers
|
// Find all the element scoped shortcode markers
|
||||||
$shortcodes = $htmlvalue->query('//img[@class="'.self::$marker_class.'"]');
|
$shortcodes = $htmlvalue->query('//img[@class="'.self::$marker_class.'"]');
|
||||||
|
|
||||||
// Find the parents. Do this before DOM modification, since SPLIT might cause parents to move otherwise
|
// Find the parents. Do this before DOM modification, since SPLIT might cause parents to move otherwise
|
||||||
$parents = $this->findParentsForMarkers($shortcodes);
|
$parents = $this->findParentsForMarkers($shortcodes);
|
||||||
|
|
||||||
/** @var DOMElement $shortcode */
|
/** @var DOMElement $shortcode */
|
||||||
foreach($shortcodes as $shortcode) {
|
foreach($shortcodes as $shortcode) {
|
||||||
$tag = $tags[$shortcode->getAttribute('data-tagid')];
|
$tag = $tags[$shortcode->getAttribute('data-tagid')];
|
||||||
$parent = $parents[$shortcode->getAttribute('data-parentid')];
|
$parent = $parents[$shortcode->getAttribute('data-parentid')];
|
||||||
|
|
||||||
$class = null;
|
$class = null;
|
||||||
if(!empty($tag['attrs']['location'])) $class = $tag['attrs']['location'];
|
if(!empty($tag['attrs']['location'])) $class = $tag['attrs']['location'];
|
||||||
else if(!empty($tag['attrs']['class'])) $class = $tag['attrs']['class'];
|
else if(!empty($tag['attrs']['class'])) $class = $tag['attrs']['class'];
|
||||||
|
|
||||||
$location = self::INLINE;
|
$location = self::INLINE;
|
||||||
if($class == 'left' || $class == 'right') $location = self::BEFORE;
|
if($class == 'left' || $class == 'right') $location = self::BEFORE;
|
||||||
if($class == 'center' || $class == 'leftALone') $location = self::SPLIT;
|
if($class == 'center' || $class == 'leftALone') $location = self::SPLIT;
|
||||||
|
|
||||||
if(!$parent) {
|
if(!$parent) {
|
||||||
if($location !== self::INLINE) {
|
if($location !== self::INLINE) {
|
||||||
user_error("Parent block for shortcode couldn't be found, but location wasn't INLINE",
|
user_error("Parent block for shortcode couldn't be found, but location wasn't INLINE",
|
||||||
E_USER_ERROR);
|
E_USER_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
$this->moveMarkerToCompliantHome($shortcode, $parent, $location);
|
||||||
$this->moveMarkerToCompliantHome($shortcode, $parent, $location);
|
}
|
||||||
|
|
||||||
|
$this->replaceMarkerWithContent($shortcode, $tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->replaceMarkerWithContent($shortcode, $tag);
|
$content = $htmlvalue->getContent();
|
||||||
|
|
||||||
|
// Clean up any marker classes left over, for example, those injected into <script> tags
|
||||||
|
$parser = $this;
|
||||||
|
$content = preg_replace_callback(
|
||||||
|
// Not a general-case parser; assumes that the HTML generated in replaceElementTagsWithMarkers()
|
||||||
|
// hasn't been heavily modified
|
||||||
|
'/<img[^>]+class="'.preg_quote(self::$marker_class).'"[^>]+data-tagid="([^"]+)"[^>]+>/i',
|
||||||
|
function ($matches) use ($tags, $parser) {
|
||||||
|
$tag = $tags[$matches[1]];
|
||||||
|
return $parser->getShortcodeReplacementText($tag);
|
||||||
|
},
|
||||||
|
$content
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = $htmlvalue->getContent();
|
$this->extend('onAfterParse', $content);
|
||||||
|
|
||||||
// Clean up any marker classes left over, for example, those injected into <script> tags
|
|
||||||
$parser = $this;
|
|
||||||
$content = preg_replace_callback(
|
|
||||||
// Not a general-case parser; assumes that the HTML generated in replaceElementTagsWithMarkers()
|
|
||||||
// hasn't been heavily modified
|
|
||||||
'/<img[^>]+class="'.preg_quote(self::$marker_class).'"[^>]+data-tagid="([^"]+)"[^>]+>/i',
|
|
||||||
function ($matches) use ($tags, $parser) {
|
|
||||||
$tag = $tags[$matches[1]];
|
|
||||||
return $parser->getShortcodeReplacementText($tag);
|
|
||||||
},
|
|
||||||
$content
|
|
||||||
);
|
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,6 @@ use SilverStripe\Forms\Form;
|
|||||||
use SilverStripe\Forms\RequiredFields;
|
use SilverStripe\Forms\RequiredFields;
|
||||||
use SilverStripe\View\ArrayData;
|
use SilverStripe\View\ArrayData;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @package framework
|
* @package framework
|
||||||
* @subpackage tests
|
* @subpackage tests
|
||||||
@ -235,28 +231,77 @@ class CheckboxSetFieldTest extends SapphireTest {
|
|||||||
$tag1 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag1');
|
$tag1 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag1');
|
||||||
$tag2 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag2');
|
$tag2 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag2');
|
||||||
$tag3 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag3');
|
$tag3 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag3');
|
||||||
$field = CheckboxSetField::create('Test', 'Testing', $checkboxTestArticle->Tags() ->map());
|
$field = CheckboxSetField::create('Test', 'Testing', $checkboxTestArticle->Tags());
|
||||||
$validator = new RequiredFields();
|
$validator = new RequiredFields();
|
||||||
$field->setValue(array(
|
$field->setValue(array( $tag1->ID, $tag2->ID ));
|
||||||
$tag1->ID => $tag1->ID,
|
$isValid = $field->validate($validator);
|
||||||
$tag2->ID => $tag2->ID
|
|
||||||
));
|
|
||||||
$this->assertTrue(
|
$this->assertTrue(
|
||||||
$field->validate($validator),
|
$isValid,
|
||||||
'Validates values in source map'
|
'Validates values in source map'
|
||||||
);
|
);
|
||||||
//invalid value should fail
|
|
||||||
|
// Invalid value should fail
|
||||||
|
$validator = new RequiredFields();
|
||||||
$fakeID = CheckboxSetFieldTest_Tag::get()->max('ID') + 1;
|
$fakeID = CheckboxSetFieldTest_Tag::get()->max('ID') + 1;
|
||||||
$field->setValue(array($fakeID => $fakeID));
|
$field->setValue(array($fakeID));
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$field->validate($validator),
|
$field->validate($validator),
|
||||||
'Field does not valid values outside of source map'
|
'Field does not valid values outside of source map'
|
||||||
);
|
);
|
||||||
//non valid value included with valid options should succeed
|
$errors = $validator->getErrors();
|
||||||
|
$error = reset($errors);
|
||||||
|
$this->assertEquals(
|
||||||
|
_t(
|
||||||
|
'MultiSelectField.SOURCE_VALIDATION',
|
||||||
|
"Please select values within the list provided. Invalid option(s) {value} given",
|
||||||
|
array('value' => $fakeID)
|
||||||
|
),
|
||||||
|
$error['message']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Multiple invalid values should fail
|
||||||
|
$validator = new RequiredFields();
|
||||||
|
$fakeID = CheckboxSetFieldTest_Tag::get()->max('ID') + 1;
|
||||||
|
$field->setValue(array($fakeID, $tag3->ID));
|
||||||
|
$this->assertFalse(
|
||||||
|
$field->validate($validator),
|
||||||
|
'Field does not valid values outside of source map'
|
||||||
|
);
|
||||||
|
$errors = $validator->getErrors();
|
||||||
|
$error = reset($errors);
|
||||||
|
$this->assertEquals(
|
||||||
|
_t(
|
||||||
|
'MultiSelectField.SOURCE_VALIDATION',
|
||||||
|
"Please select values within the list provided. Invalid option(s) {value} given",
|
||||||
|
array('value' => implode(',', [$fakeID, $tag3->ID]))
|
||||||
|
),
|
||||||
|
$error['message']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Invalid value with non-array value
|
||||||
|
$validator = new RequiredFields();
|
||||||
|
$field->setValue($fakeID);
|
||||||
|
$this->assertFalse(
|
||||||
|
$field->validate($validator),
|
||||||
|
'Field does not valid values outside of source map'
|
||||||
|
);
|
||||||
|
$errors = $validator->getErrors();
|
||||||
|
$error = reset($errors);
|
||||||
|
$this->assertEquals(
|
||||||
|
_t(
|
||||||
|
'MultiSelectField.SOURCE_VALIDATION',
|
||||||
|
"Please select values within the list provided. Invalid option(s) {value} given",
|
||||||
|
array('value' => $fakeID)
|
||||||
|
),
|
||||||
|
$error['message']
|
||||||
|
);
|
||||||
|
|
||||||
|
// non valid value included with valid options should succeed
|
||||||
|
$validator = new RequiredFields();
|
||||||
$field->setValue(array(
|
$field->setValue(array(
|
||||||
$tag1->ID => $tag1->ID,
|
$tag1->ID,
|
||||||
$tag2->ID => $tag2->ID,
|
$tag2->ID,
|
||||||
$tag3->ID => $tag3->ID
|
$tag3->ID
|
||||||
));
|
));
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$field->validate($validator),
|
$field->validate($validator),
|
||||||
|
@ -8,7 +8,7 @@ use SilverStripe\Forms\RequiredFields;
|
|||||||
use SilverStripe\Forms\FieldList;
|
use SilverStripe\Forms\FieldList;
|
||||||
use SilverStripe\Forms\Form;
|
use SilverStripe\Forms\Form;
|
||||||
use SilverStripe\View\ArrayData;
|
use SilverStripe\View\ArrayData;
|
||||||
|
use SilverStripe\ORM\Map;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,12 +18,37 @@ use SilverStripe\View\ArrayData;
|
|||||||
class DropdownFieldTest extends SapphireTest {
|
class DropdownFieldTest extends SapphireTest {
|
||||||
|
|
||||||
public function testGetSource() {
|
public function testGetSource() {
|
||||||
$source = array(1=>'one');
|
$source = array(1=>'one', 2 => 'two');
|
||||||
$field = new DropdownField('Field', null, $source);
|
$field = new DropdownField('Field', null, $source);
|
||||||
|
$this->assertEquals(
|
||||||
|
$source,
|
||||||
|
$field->getSource()
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
$source,
|
||||||
|
$field->getSource()
|
||||||
|
);
|
||||||
|
|
||||||
|
$items = new ArrayList(array(
|
||||||
|
array( 'ID' => 1, 'Title' => 'ichi', 'OtherField' => 'notone' ),
|
||||||
|
array( 'ID' => 2, 'Title' => 'ni', 'OtherField' => 'nottwo' ),
|
||||||
|
));
|
||||||
|
$field->setSource($items);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$field->getSource(),
|
$field->getSource(),
|
||||||
array(
|
array(
|
||||||
1 => 'one'
|
1 => 'ichi',
|
||||||
|
2 => 'ni',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$map = new Map($items, 'ID', 'OtherField');
|
||||||
|
$field->setSource($map);
|
||||||
|
$this->assertEquals(
|
||||||
|
$field->getSource(),
|
||||||
|
array(
|
||||||
|
1 => 'notone',
|
||||||
|
2 => 'nottwo',
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -257,61 +257,115 @@ class ArrayListTest extends SapphireTest {
|
|||||||
|
|
||||||
public function testSortSimpleDefaultIsSortedASC() {
|
public function testSortSimpleDefaultIsSortedASC() {
|
||||||
$list = new ArrayList(array(
|
$list = new ArrayList(array(
|
||||||
array('Name' => 'Steve'),
|
array('Name' => 'Steve'),
|
||||||
(object) array('Name' => 'Bob'),
|
(object) array('Name' => 'Bob'),
|
||||||
array('Name' => 'John'),
|
array('Name' => 'John'),
|
||||||
array('Name' => 'bonny'),
|
array('Name' => 'bonny'),
|
||||||
array('Name' => 'bonny1'),
|
|
||||||
array('Name' => 'bonny10'),
|
|
||||||
array('Name' => 'bonny2'),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// Unquoted name
|
// Unquoted name
|
||||||
$list1 = $list->sort('Name');
|
$list1 = $list->sort('Name');
|
||||||
$this->assertEquals(array(
|
$this->assertEquals(array(
|
||||||
(object) array('Name' => 'Bob'),
|
(object) array('Name' => 'Bob'),
|
||||||
array('Name' => 'bonny'),
|
array('Name' => 'bonny'),
|
||||||
array('Name' => 'bonny1'),
|
array('Name' => 'John'),
|
||||||
array('Name' => 'bonny2'),
|
array('Name' => 'Steve'),
|
||||||
array('Name' => 'bonny10'),
|
), $list1->toArray());
|
||||||
array('Name' => 'John'),
|
|
||||||
array('Name' => 'Steve'),
|
|
||||||
), $list1->toArray());
|
|
||||||
|
|
||||||
// Quoted name name
|
// Quoted name name
|
||||||
$list2 = $list->sort('"Name"');
|
$list2 = $list->sort('"Name"');
|
||||||
$this->assertEquals(array(
|
$this->assertEquals(array(
|
||||||
(object) array('Name' => 'Bob'),
|
(object) array('Name' => 'Bob'),
|
||||||
array('Name' => 'bonny'),
|
array('Name' => 'bonny'),
|
||||||
array('Name' => 'bonny1'),
|
array('Name' => 'John'),
|
||||||
array('Name' => 'bonny2'),
|
array('Name' => 'Steve'),
|
||||||
array('Name' => 'bonny10'),
|
), $list2->toArray());
|
||||||
array('Name' => 'John'),
|
|
||||||
array('Name' => 'Steve'),
|
|
||||||
), $list2->toArray());
|
|
||||||
|
|
||||||
// Array (non-associative)
|
// Array (non-associative)
|
||||||
$list3 = $list->sort(array('"Name"'));
|
$list3 = $list->sort(array('"Name"'));
|
||||||
$this->assertEquals(array(
|
$this->assertEquals(array(
|
||||||
(object) array('Name' => 'Bob'),
|
(object) array('Name' => 'Bob'),
|
||||||
array('Name' => 'bonny'),
|
array('Name' => 'bonny'),
|
||||||
array('Name' => 'bonny1'),
|
array('Name' => 'John'),
|
||||||
array('Name' => 'bonny2'),
|
array('Name' => 'Steve'),
|
||||||
array('Name' => 'bonny10'),
|
), $list3->toArray());
|
||||||
array('Name' => 'John'),
|
|
||||||
array('Name' => 'Steve'),
|
// Quoted name name with table
|
||||||
), $list3->toArray());
|
$list4 = $list->sort('"Record"."Name"');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
(object) array('Name' => 'Bob'),
|
||||||
|
array('Name' => 'bonny'),
|
||||||
|
array('Name' => 'John'),
|
||||||
|
array('Name' => 'Steve')
|
||||||
|
), $list4->toArray());
|
||||||
|
|
||||||
|
// Quoted name name with table (desc)
|
||||||
|
$list5 = $list->sort('"Record"."Name" DESC');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
array('Name' => 'Steve'),
|
||||||
|
array('Name' => 'John'),
|
||||||
|
array('Name' => 'bonny'),
|
||||||
|
(object) array('Name' => 'Bob')
|
||||||
|
), $list5->toArray());
|
||||||
|
|
||||||
|
// Table without quotes
|
||||||
|
$list6 = $list->sort('Record.Name');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
(object) array('Name' => 'Bob'),
|
||||||
|
array('Name' => 'bonny'),
|
||||||
|
array('Name' => 'John'),
|
||||||
|
array('Name' => 'Steve')
|
||||||
|
), $list6->toArray());
|
||||||
|
|
||||||
// Check original list isn't altered
|
// Check original list isn't altered
|
||||||
$this->assertEquals(array(
|
$this->assertEquals(array(
|
||||||
array('Name' => 'Steve'),
|
array('Name' => 'Steve'),
|
||||||
|
(object) array('Name' => 'Bob'),
|
||||||
|
array('Name' => 'John'),
|
||||||
|
array('Name' => 'bonny'),
|
||||||
|
), $list->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMixedCaseSort() {
|
||||||
|
// Note: Natural sorting is not expected, so if 'bonny10' were included
|
||||||
|
// below we would expect it to appear between bonny1 and bonny2. That's
|
||||||
|
// undesirable though so we're not enforcing it in tests.
|
||||||
|
$original = array(
|
||||||
|
array('Name' => 'Steve'),
|
||||||
|
(object) array('Name' => 'Bob'),
|
||||||
|
array('Name' => 'John'),
|
||||||
|
array('Name' => 'bonny'),
|
||||||
|
array('Name' => 'bonny1'),
|
||||||
|
//array('Name' => 'bonny10'),
|
||||||
|
array('Name' => 'bonny2'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$list = new ArrayList($original);
|
||||||
|
|
||||||
|
$expected = array(
|
||||||
(object) array('Name' => 'Bob'),
|
(object) array('Name' => 'Bob'),
|
||||||
array('Name' => 'John'),
|
|
||||||
array('Name' => 'bonny'),
|
array('Name' => 'bonny'),
|
||||||
array('Name' => 'bonny1'),
|
array('Name' => 'bonny1'),
|
||||||
array('Name' => 'bonny10'),
|
//array('Name' => 'bonny10'),
|
||||||
array('Name' => 'bonny2'),
|
array('Name' => 'bonny2'),
|
||||||
), $list->toArray());
|
array('Name' => 'John'),
|
||||||
|
array('Name' => 'Steve'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unquoted name
|
||||||
|
$list1 = $list->sort('Name');
|
||||||
|
$this->assertEquals($expected, $list1->toArray());
|
||||||
|
|
||||||
|
// Quoted name name
|
||||||
|
$list2 = $list->sort('"Name"');
|
||||||
|
$this->assertEquals($expected, $list2->toArray());
|
||||||
|
|
||||||
|
// Array (non-associative)
|
||||||
|
$list3 = $list->sort(array('"Name"'));
|
||||||
|
$this->assertEquals($expected, $list3->toArray());
|
||||||
|
|
||||||
|
// Check original list isn't altered
|
||||||
|
$this->assertEquals($original, $list->toArray());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,6 +463,42 @@ class ArrayListTest extends SapphireTest {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSortNumeric() {
|
||||||
|
$list = new ArrayList(array(
|
||||||
|
array('Sort' => 0),
|
||||||
|
array('Sort' => -1),
|
||||||
|
array('Sort' => 1),
|
||||||
|
array('Sort' => -2),
|
||||||
|
array('Sort' => 2),
|
||||||
|
array('Sort' => -10),
|
||||||
|
array('Sort' => 10)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Sort descending
|
||||||
|
$list1 = $list->sort('Sort', 'DESC');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
array('Sort' => 10),
|
||||||
|
array('Sort' => 2),
|
||||||
|
array('Sort' => 1),
|
||||||
|
array('Sort' => 0),
|
||||||
|
array('Sort' => -1),
|
||||||
|
array('Sort' => -2),
|
||||||
|
array('Sort' => -10)
|
||||||
|
), $list1->toArray());
|
||||||
|
|
||||||
|
// Sort ascending
|
||||||
|
$list1 = $list->sort('Sort', 'ASC');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
array('Sort' => -10),
|
||||||
|
array('Sort' => -2),
|
||||||
|
array('Sort' => -1),
|
||||||
|
array('Sort' => 0),
|
||||||
|
array('Sort' => 1),
|
||||||
|
array('Sort' => 2),
|
||||||
|
array('Sort' => 10)
|
||||||
|
), $list1->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
public function testReverse() {
|
public function testReverse() {
|
||||||
$list = new ArrayList(array(
|
$list = new ArrayList(array(
|
||||||
array('Name' => 'John'),
|
array('Name' => 'John'),
|
||||||
|
@ -171,6 +171,11 @@ class DBFieldTest extends SapphireTest {
|
|||||||
$this->assertEquals("00:00:00", $time->getValue());
|
$this->assertEquals("00:00:00", $time->getValue());
|
||||||
$time->setValue('00:00:00');
|
$time->setValue('00:00:00');
|
||||||
$this->assertEquals("00:00:00", $time->getValue());
|
$this->assertEquals("00:00:00", $time->getValue());
|
||||||
|
|
||||||
|
/* BigInt behaviour */
|
||||||
|
$bigInt = singleton('SilverStripe\\ORM\FieldType\\DBBigInt');
|
||||||
|
$bigInt->setValue(PHP_INT_MAX);
|
||||||
|
$this->assertEquals(PHP_INT_MAX, $bigInt->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExists() {
|
public function testExists() {
|
||||||
|
@ -353,6 +353,17 @@ class DataListTest extends SapphireTest {
|
|||||||
$this->assertEquals($otherExpected, $otherMap);
|
$this->assertEquals($otherExpected, $otherMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAmbiguousAggregate() {
|
||||||
|
// Test that we avoid ambiguity error when a field exists on two joined tables
|
||||||
|
// Fetch the sponsors in a round-about way to simulate this
|
||||||
|
$teamID = $this->idFromFixture('DataObjectTest_Team','team2');
|
||||||
|
$sponsors = DataObjectTest_EquipmentCompany::get()->filter('SponsoredTeams.ID', $teamID);
|
||||||
|
$this->assertNotNull($sponsors->Max('ID'));
|
||||||
|
$this->assertNotNull($sponsors->Min('ID'));
|
||||||
|
$this->assertNotNull($sponsors->Avg('ID'));
|
||||||
|
$this->assertNotNull($sponsors->Sum('ID'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testEach() {
|
public function testEach() {
|
||||||
$list = DataObjectTest_TeamComment::get();
|
$list = DataObjectTest_TeamComment::get();
|
||||||
|
|
||||||
@ -544,6 +555,34 @@ class DataListTest extends SapphireTest {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function testSortNumeric() {
|
||||||
|
$list = DataObjectTest_Sortable::get();
|
||||||
|
$list1 = $list->sort('Sort', 'ASC');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
-10,
|
||||||
|
-2,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
10
|
||||||
|
), $list1->column('Sort'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSortMixedCase() {
|
||||||
|
$list = DataObjectTest_Sortable::get();
|
||||||
|
$list1 = $list->sort('Name', 'ASC');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'Bob',
|
||||||
|
'bonny',
|
||||||
|
'jane',
|
||||||
|
'John',
|
||||||
|
'sam',
|
||||||
|
'Steve',
|
||||||
|
'steven'
|
||||||
|
), $list1->column('Name'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test DataList->canFilterBy()
|
* Test DataList->canFilterBy()
|
||||||
*/
|
*/
|
||||||
|
@ -40,6 +40,7 @@ class DataObjectTest extends SapphireTest {
|
|||||||
'DataObjectTest_EquipmentCompany',
|
'DataObjectTest_EquipmentCompany',
|
||||||
'DataObjectTest_SubEquipmentCompany',
|
'DataObjectTest_SubEquipmentCompany',
|
||||||
'DataObjectTest\NamespacedClass',
|
'DataObjectTest\NamespacedClass',
|
||||||
|
'DataObjectTest_Sortable',
|
||||||
'DataObjectTest\RelationClass',
|
'DataObjectTest\RelationClass',
|
||||||
'DataObjectTest_ExtendedTeamComment',
|
'DataObjectTest_ExtendedTeamComment',
|
||||||
'DataObjectTest_Company',
|
'DataObjectTest_Company',
|
||||||
@ -1771,6 +1772,20 @@ class DataObjectTest extends SapphireTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testBigIntField() {
|
||||||
|
$staff = new DataObjectTest_Staff();
|
||||||
|
$staff->Salary = PHP_INT_MAX;
|
||||||
|
$staff->write();
|
||||||
|
$this->assertEquals(PHP_INT_MAX, DataObjectTest_Staff::get()->byID($staff->ID)->Salary);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataObjectTest_Sortable extends DataObject implements TestOnly {
|
||||||
|
private static $db = array(
|
||||||
|
'Sort' => 'Int',
|
||||||
|
'Name' => 'Varchar',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DataObjectTest_Player extends Member implements TestOnly {
|
class DataObjectTest_Player extends Member implements TestOnly {
|
||||||
@ -1988,11 +2003,14 @@ class DataObjectTest_EquipmentCompany extends DataObjectTest_Company implements
|
|||||||
|
|
||||||
class DataObjectTest_SubEquipmentCompany extends DataObjectTest_EquipmentCompany implements TestOnly {
|
class DataObjectTest_SubEquipmentCompany extends DataObjectTest_EquipmentCompany implements TestOnly {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'SubclassDatabaseField' => 'Varchar'
|
'SubclassDatabaseField' => 'Varchar',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DataObjectTest_Staff extends DataObject implements TestOnly {
|
class DataObjectTest_Staff extends DataObject implements TestOnly {
|
||||||
|
private static $db = array(
|
||||||
|
'Salary' => 'BigInt',
|
||||||
|
);
|
||||||
private static $has_one = array (
|
private static $has_one = array (
|
||||||
'CurrentCompany' => 'DataObjectTest_Company',
|
'CurrentCompany' => 'DataObjectTest_Company',
|
||||||
'PreviousCompany' => 'DataObjectTest_Company'
|
'PreviousCompany' => 'DataObjectTest_Company'
|
||||||
|
@ -1,3 +1,25 @@
|
|||||||
|
DataObjectTest_Sortable:
|
||||||
|
numeric1:
|
||||||
|
Sort: 0
|
||||||
|
Name: steven
|
||||||
|
numeric2:
|
||||||
|
Sort: -1
|
||||||
|
Name: bonny
|
||||||
|
numeric3:
|
||||||
|
Sort: 1
|
||||||
|
Name: sam
|
||||||
|
numeric4:
|
||||||
|
Sort: -2
|
||||||
|
Name: Bob
|
||||||
|
numeric5:
|
||||||
|
Sort: 2
|
||||||
|
Name: jane
|
||||||
|
numeric6:
|
||||||
|
Sort: -10
|
||||||
|
Name: Steve
|
||||||
|
numeric7:
|
||||||
|
Sort: 10
|
||||||
|
Name: John
|
||||||
DataObjectTest_EquipmentCompany:
|
DataObjectTest_EquipmentCompany:
|
||||||
equipmentcompany1:
|
equipmentcompany1:
|
||||||
Name: Company corp
|
Name: Company corp
|
||||||
|
Loading…
Reference in New Issue
Block a user