diff --git a/.travis.yml b/.travis.yml
index d8e4471e7..b09f51393 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -43,7 +43,7 @@ matrix:
- sudo apt-get install -y tidy
before_script:
- - composer self-update
+ - composer self-update || true
- phpenv rehash
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- "if [ \"$BEHAT_TEST\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi"
diff --git a/_config/config.yml b/_config/config.yml
index a6fa72c8d..31dbd1682 100644
--- a/_config/config.yml
+++ b/_config/config.yml
@@ -5,4 +5,9 @@ Upload:
# Replace an existing file rather than renaming the new one.
replaceFile: false
MySQLDatabase:
- connection_charset: utf8
\ No newline at end of file
+ connection_charset: utf8
+HTTP:
+ cache_control:
+ max-age: 0
+ must-revalidate: "true"
+ no-transform: "true"
\ No newline at end of file
diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php
index d15c46596..389f60c03 100644
--- a/admin/code/LeftAndMain.php
+++ b/admin/code/LeftAndMain.php
@@ -339,8 +339,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
if (Director::isDev()) Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/leaktools.js');
- HTMLEditorField::include_js();
-
$leftAndMainIncludes = array_unique(array_merge(
array(
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Layout.js',
@@ -832,16 +830,19 @@ class LeftAndMain extends Controller implements PermissionProvider {
$record = ($rootID) ? $this->getRecord($rootID) : null;
$obj = $record ? $record : singleton($className);
+ // Get the current page
+ // NOTE: This *must* be fetched before markPartialTree() is called, as this
+ // causes the Hierarchy::$marked cache to be flushed (@see CMSMain::getRecord)
+ // which means that deleted pages stored in the marked tree would be removed
+ $currentPage = $this->currentPage();
+
// Mark the nodes of the tree to return
if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction);
$obj->markPartialTree($nodeCountThreshold, $this, $childrenMethod, $numChildrenMethod);
// Ensure current page is exposed
- // This call flushes the Hierarchy::$marked cache when the current node is deleted
- // @see CMSMain::getRecord()
- // This will make it impossible to show children under a deleted parent page
- // if($p = $this->currentPage()) $obj->markToExpose($p);
+ if($currentPage) $obj->markToExpose($currentPage);
// NOTE: SiteTree/CMSMain coupling :-(
if(class_exists('SiteTree')) {
diff --git a/admin/css/screen.css b/admin/css/screen.css
index 3381ba5f4..41853c695 100644
--- a/admin/css/screen.css
+++ b/admin/css/screen.css
@@ -140,7 +140,7 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif;
.ui-accordion .ui-accordion-header { border-color: #c0c0c2; margin-bottom: 0; }
.ui-accordion .ui-accordion-content { border: 1px solid #c0c0c2; border-top: none; }
-.ui-autocomplete { max-height: 240px; overflow-x: hidden; overflow-y: auto; }
+.ui-autocomplete { max-height: 240px; overflow-x: hidden; overflow-y: auto; /** sorry about the !important but the specificity of other selectors mandates it over writing out very specific selectors **/ }
.ui-autocomplete-loading { background-image: url(../images/throbber.gif) !important; background-position: 97% center !important; background-repeat: no-repeat !important; background-size: auto !important; }
/** This file defines common styles for form elements used throughout the CMS interface. It is an addition to the base styles defined in framework/css/Form.css. @package framework @subpackage admin */
@@ -233,7 +233,7 @@ form.small .field input.text, form.small .field textarea, form.small .field sele
.cms .ss-ui-button.ui-state-hover, .cms .ss-ui-button:hover { text-decoration: none; background-color: white; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e6e6e6)); background: -moz-linear-gradient(#ffffff, #e6e6e6); background: -webkit-linear-gradient(#ffffff, #e6e6e6); background: linear-gradient(#ffffff, #e6e6e6); -moz-box-shadow: 0 0 5px #b3b3b3; -webkit-box-shadow: 0 0 5px #b3b3b3; box-shadow: 0 0 5px #b3b3b3; }
.cms .ss-ui-button:active, .cms .ss-ui-button:focus, .cms .ss-ui-button.ui-state-active, .cms .ss-ui-button.ui-state-focus { border: 1px solid #b3b3b3; background-color: white; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e6e6e6)); background: -moz-linear-gradient(#ffffff, #e6e6e6); background: -webkit-linear-gradient(#ffffff, #e6e6e6); background: linear-gradient(#ffffff, #e6e6e6); -moz-box-shadow: 0 0 5px #b3b3b3 inset; -webkit-box-shadow: 0 0 5px #b3b3b3 inset; box-shadow: 0 0 5px #b3b3b3 inset; }
.cms .ss-ui-button.ss-ui-action-minor span { padding-left: 0; padding-right: 0; }
-.cms .ss-ui-button.ss-ui-action-constructive { text-shadow: none; font-weight: bold; color: white; border-color: #1F9433; border-bottom-color: #166a24; background-color: #1F9433; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzkzYmU0MiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzFmOTQzMyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #93be42), color-stop(100%, #1f9433)); background: -moz-linear-gradient(#93be42, #1f9433); background: -webkit-linear-gradient(#93be42, #1f9433); background: linear-gradient(#93be42, #1f9433); text-shadow: #1c872f 0 -1px -1px; }
+.cms .ss-ui-button.ss-ui-action-constructive { text-shadow: none; font-weight: bold; color: white; border-color: #1F9433; border-bottom-color: #166a24; background-color: #1F9433; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzk0YmU0MiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzFmOTQzMyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #94be42), color-stop(100%, #1f9433)); background: -moz-linear-gradient(#94be42, #1f9433); background: -webkit-linear-gradient(#94be42, #1f9433); background: linear-gradient(#94be42, #1f9433); text-shadow: #1c872f 0 -1px -1px; }
.cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover, .cms .ss-ui-button.ss-ui-action-constructive:hover { border-color: #166a24; background-color: #1F9433; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2E0Y2EzYSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzIzYTkzYSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a4ca3a), color-stop(100%, #23a93a)); background: -moz-linear-gradient(#a4ca3a, #23a93a); background: -webkit-linear-gradient(#a4ca3a, #23a93a); background: linear-gradient(#a4ca3a, #23a93a); }
.cms .ss-ui-button.ss-ui-action-constructive:active, .cms .ss-ui-button.ss-ui-action-constructive:focus, .cms .ss-ui-button.ss-ui-action-constructive.ui-state-active, .cms .ss-ui-button.ss-ui-action-constructive.ui-state-focus { background-color: #1d8c30; -moz-box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); -webkit-box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); }
.cms .ss-ui-button.ss-ui-action-destructive { color: #f00; background-color: #e6e6e6; }
@@ -265,6 +265,7 @@ form.small .field input.text, form.small .field textarea, form.small .field sele
.ss-toggle .ui-accordion-content .field:last-child { margin-bottom: 0; }
.ss-toggle .ui-accordion-content .field .middleColumn { margin-left: 0; }
.ss-toggle .ui-accordion-content .field label { float: none; margin-left: 0; }
+.ss-toggle .ui-accordion-content .field label.ss-ui-button { float: left; }
.ss-toggle .ui-accordion-content .field .description { margin-left: 0; }
/** ---------------------------------------------------- Checkbox Field ---------------------------------------------------- */
@@ -687,6 +688,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai
.htmleditorfield-dialog .CompositeField .text select { margin: 5px 0 0 0; }
.htmleditorfield-linkform .step2 { margin-bottom: 16px; }
+.htmleditorfield-linkform .ss-uploadfield .middleColumn { width: auto; }
.htmleditorfield-mediaform .ss-gridfield .gridfield-button-delete { display: none; }
.htmleditorfield-mediaform .ss-gridfield table.ss-gridfield-table tbody td:first-child { padding: 0; text-align: center; }
@@ -846,15 +848,15 @@ form.import-form label.left { width: 250px; }
.tree-holder.jstree span.comment-count:before, .cms-tree.jstree span.comment-count:before { content: ""; position: absolute; border-style: solid; display: block; width: 0; bottom: -4px; /* value = - border-top-width - border-bottom-width */ left: 3px; /* controls horizontal position */ border-width: 4px 4px 0; border-color: #C9B800 transparent; }
.tree-holder.jstree span.comment-count:after, .cms-tree.jstree span.comment-count:after { content: ""; position: absolute; border-style: solid; /* reduce the damage in FF3.0 */ display: block; width: 0; bottom: -3px; /* value = - border-top-width - border-bottom-width */ left: 4px; /* value = (:before left) + (:before border-left) - (:after border-left) */ border-width: 3px 3px 0; border-color: #FFF0BC transparent; }
.tree-holder.jstree .jstree-hovered, .cms-tree.jstree .jstree-hovered { text-shadow: none; text-decoration: none; }
-.tree-holder.jstree .jstree-closed > ins, .cms-tree.jstree .jstree-closed > ins { background-position: 0 0; }
-.tree-holder.jstree .jstree-open > ins, .cms-tree.jstree .jstree-open > ins { background-position: -20px 0; }
+.tree-holder.jstree .jstree-closed > ins, .cms-tree.jstree .jstree-closed > ins { background-position: 2px -1px; }
+.tree-holder.jstree .jstree-open > ins, .cms-tree.jstree .jstree-open > ins { background-position: -18px -1px; }
.tree-holder.filtered-list li:not(.filtered-item) > a, .cms-tree.filtered-list li:not(.filtered-item) > a { color: #aaa; }
-.cms-content-fields .cms-tree.jstree .jstree-no-checkboxes li a { padding-left: 15px; }
-.cms-content-fields .cms-tree.jstree .jstree-no-checkboxes li .jstree-hovered, .cms-content-fields .cms-tree.jstree .jstree-no-checkboxes li .jstree-clicked, .cms-content-fields .cms-tree.jstree .jstree-no-checkboxes li a:focus { padding-left: 3px; }
-.cms-content-fields .cms-tree.jstree .jstree-no-checkboxes li .jstree-hovered .jstree-icon, .cms-content-fields .cms-tree.jstree .jstree-no-checkboxes li .jstree-clicked .jstree-icon, .cms-content-fields .cms-tree.jstree .jstree-no-checkboxes li a:focus .jstree-icon { display: block; }
+.cms-tree.jstree .jstree-no-checkboxes li a { padding-left: 12px; }
+.cms-tree.jstree .jstree-no-checkboxes li .jstree-hovered, .cms-tree.jstree .jstree-no-checkboxes li .jstree-clicked, .cms-tree.jstree .jstree-no-checkboxes li a:focus { padding-left: 0; }
+.cms-tree.jstree .jstree-no-checkboxes li .jstree-hovered .jstree-icon, .cms-tree.jstree .jstree-no-checkboxes li .jstree-clicked .jstree-icon, .cms-tree.jstree .jstree-no-checkboxes li a:focus .jstree-icon { display: block; }
-.jstree-default a .jstree-icon, .jstree-default-rtl a .jstree-icon, .jstree-classic a .jstree-icon, .jstree-apple a .jstree-icon { background-position: -62px -19px; }
+.jstree-default a .jstree-icon, .jstree-default-rtl a .jstree-icon, .jstree-classic a .jstree-icon, .jstree-apple a .jstree-icon { background-position: -60px -19px; }
/* ensure status is visible in sidebar */
.cms-content-tools .cms-tree.jstree li { min-width: 159px; }
diff --git a/admin/images/spinner.gif b/admin/images/spinner.gif
index cec3f8389..bc8cf92e5 100644
Binary files a/admin/images/spinner.gif and b/admin/images/spinner.gif differ
diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss
index a753745e3..2f2f2dd9f 100644
--- a/admin/scss/_forms.scss
+++ b/admin/scss/_forms.scss
@@ -681,6 +681,10 @@ form.small .field, .field.small {
label {
float: none;
margin-left: 0;
+
+ &.ss-ui-button {
+ float: left;
+ }
}
.description {
margin-left: 0;
diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss
index 8cc272c47..d8a33d823 100644
--- a/admin/scss/_style.scss
+++ b/admin/scss/_style.scss
@@ -1520,6 +1520,11 @@ body.cms-dialog {
.step2 {
margin-bottom: $grid-x*2;
}
+ .ss-uploadfield {
+ .middleColumn {
+ width: auto;
+ }
+ }
}
.htmleditorfield-mediaform {
diff --git a/admin/scss/_tree.scss b/admin/scss/_tree.scss
index 0885b85ca..35c89e729 100644
--- a/admin/scss/_tree.scss
+++ b/admin/scss/_tree.scss
@@ -542,10 +542,10 @@
text-decoration: none;
}
.jstree-closed > ins {
- background-position:0 0;
+ background-position:2px -1px;
}
.jstree-open > ins {
- background-position:-20px 0;
+ background-position:-18px -1px;
}
}
@@ -559,27 +559,25 @@
// For drag and drop icons to not appear whilst in multi-selection
-.cms-content-fields {
- .cms-tree {
- &.jstree {
- .jstree-no-checkboxes {
- li {
- a {
- padding-left: 15px;
- }
+.cms-tree {
+ &.jstree {
+ .jstree-no-checkboxes {
+ li {
+ a {
+ padding-left: 12px;
+ }
- .jstree-hovered,
- .jstree-clicked,
- a:focus {
- padding-left: 3px;
- }
+ .jstree-hovered,
+ .jstree-clicked,
+ a:focus {
+ padding-left: 0;
+ }
- .jstree-hovered,
- .jstree-clicked,
- a:focus {
- .jstree-icon {
- display: block;
- }
+ .jstree-hovered,
+ .jstree-clicked,
+ a:focus {
+ .jstree-icon {
+ display: block;
}
}
}
@@ -592,7 +590,7 @@
.jstree-default-rtl a .jstree-icon,
.jstree-classic a .jstree-icon,
.jstree-apple a .jstree-icon {
- background-position:-62px -19px;
+ background-position:-60px -19px;
}
/* ensure status is visible in sidebar */
diff --git a/control/CookieJar.php b/control/CookieJar.php
index 8e7211375..ab4912558 100644
--- a/control/CookieJar.php
+++ b/control/CookieJar.php
@@ -3,7 +3,7 @@
/**
* A default backend for the setting and getting of cookies
*
- * This backend allows one to better test Cookie setting and seperate cookie
+ * This backend allows one to better test Cookie setting and separate cookie
* handling from the core
*
* @todo Create a config array for defaults (eg: httpOnly, secure, path, domain, expiry)
@@ -14,7 +14,7 @@
class CookieJar implements Cookie_Backend {
/**
- * Hold the cookies that were existing at time of instatiation (ie: The ones
+ * Hold the cookies that were existing at time of instantiation (ie: The ones
* sent to PHP by the browser)
*
* @var array Existing cookies sent by the browser
@@ -30,7 +30,7 @@ class CookieJar implements Cookie_Backend {
protected $current = array();
/**
- * Hold any NEW cookies that were set by the appliation and will be sent
+ * Hold any NEW cookies that were set by the application and will be sent
* in the next response
*
* @var array New cookies set by the application
@@ -39,7 +39,7 @@ class CookieJar implements Cookie_Backend {
/**
* When creating the backend we want to store the existing cookies in our
- * "existing" array. This allows us to distinguish between cookies we recieved
+ * "existing" array. This allows us to distinguish between cookies we received
* or we set ourselves (and didn't get from the browser)
*
* @param array $cookies The existing cookies to load into the cookie jar.
@@ -92,6 +92,8 @@ class CookieJar implements Cookie_Backend {
/**
* Get the cookie value by name
*
+ * Cookie names are normalised to work around PHP's behaviour of replacing incoming variable name . with _
+ *
* @param string $name The name of the cookie to get
* @param boolean $includeUnsent Include cookies we've yet to send when fetching values
*
@@ -102,6 +104,12 @@ class CookieJar implements Cookie_Backend {
if (isset($cookies[$name])) {
return $cookies[$name];
}
+
+ //Normalise cookie names by replacing '.' with '_'
+ $safeName = str_replace('.', '_', $name);
+ if (isset($cookies[$safeName])) {
+ return $cookies[$safeName];
+ }
}
/**
diff --git a/control/HTTP.php b/control/HTTP.php
index 83b5e6310..9993c8e52 100644
--- a/control/HTTP.php
+++ b/control/HTTP.php
@@ -330,10 +330,15 @@ class HTTP {
// Popuplate $responseHeaders with all the headers that we want to build
$responseHeaders = array();
+
$config = Config::inst();
+ $cacheControlHeaders = Config::inst()->get('HTTP', 'cache_control');
+
+
// currently using a config setting to cancel this, seems to be so taht the CMS caches ajax requests
if(function_exists('apache_request_headers') && $config->get(get_called_class(), 'cache_ajax_requests')) {
$requestHeaders = apache_request_headers();
+
if(isset($requestHeaders['X-Requested-With']) && $requestHeaders['X-Requested-With']=='XMLHttpRequest') {
$cacheAge = 0;
}
@@ -344,7 +349,7 @@ class HTTP {
}
if($cacheAge > 0) {
- $responseHeaders["Cache-Control"] = "max-age={$cacheAge}, must-revalidate, no-transform";
+ $cacheControlHeaders['max-age'] = self::$cache_age;
$responseHeaders["Pragma"] = "";
// To do: User-Agent should only be added in situations where you *are* actually
@@ -368,10 +373,20 @@ class HTTP {
// IE6-IE8 have problems saving files when https and no-cache are used
// (http://support.microsoft.com/kb/323308)
// Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options.
- $responseHeaders["Cache-Control"] = "max-age=3, must-revalidate, no-transform";
+ $cacheControlHeaders['max-age'] = 3;
$responseHeaders["Pragma"] = "";
} else {
- $responseHeaders["Cache-Control"] = "no-cache, max-age=0, must-revalidate, no-transform";
+ $cacheControlHeaders['no-cache'] = "true";
+ }
+ }
+
+ foreach($cacheControlHeaders as $header => $value) {
+ if(is_null($value)) {
+ unset($cacheControlHeaders[$header]);
+ } elseif(is_bool($value) || $value === "true") {
+ $cacheControlHeaders[$header] = $header;
+ } else {
+ $cacheControlHeaders[$header] = $header."=".$value;
}
}
diff --git a/docs/en/00_Getting_Started/03_Environment_Management.md b/docs/en/00_Getting_Started/03_Environment_Management.md
index 5b6f2e943..b3f50ad7e 100644
--- a/docs/en/00_Getting_Started/03_Environment_Management.md
+++ b/docs/en/00_Getting_Started/03_Environment_Management.md
@@ -105,21 +105,21 @@ This is my `_ss_environment.php` file. I have it placed in `/var`, as each of th
| Name | Description |
| ---- | ----------- |
-| `TEMP_FOLDER` | Absolute file path to store temporary files such as cached templates or the class manifest. Needs to be writeable by the webserver user. Defaults to *silverstripe-cache* in the webroot, and falls back to *sys_get_temp_dir()*. See *getTempFolder()* in *framework/core/TempPath.php* |
-| `SS_DATABASE_CLASS` | The database class to use, MySQLDatabase, MSSQLDatabase, etc. defaults to MySQLDatabase|
-| `SS_DATABASE_SERVER`| The database server to use, defaulting to localhost|
-| `SS_DATABASE_USERNAME`| The database username (mandatory)|
-| `SS_DATABASE_PASSWORD`| The database password (mandatory)|
-| `SS_DATABASE_PORT`| The database port|
+| `TEMP_FOLDER` | Absolute file path to store temporary files such as cached templates or the class manifest. Needs to be writeable by the webserver user. Defaults to *silverstripe-cache* in the webroot, and falls back to *sys_get_temp_dir()*. See *getTempFolder()* in *framework/core/TempPath.php*.|
+| `SS_DATABASE_CLASS` | The database class to use, MySQLDatabase, MSSQLDatabase, etc. defaults to MySQLDatabase.|
+| `SS_DATABASE_SERVER`| The database server to use, defaulting to localhost.|
+| `SS_DATABASE_USERNAME`| The database username (mandatory).|
+| `SS_DATABASE_PASSWORD`| The database password (mandatory).|
+| `SS_DATABASE_PORT`| The database port.|
| `SS_DATABASE_SUFFIX`| A suffix to add to the database name.|
| `SS_DATABASE_PREFIX`| A prefix to add to the database name.|
| `SS_DATABASE_TIMEZONE`| Set the database timezone to something other than the system timezone.
| `SS_DATABASE_NAME` | Set the database name. Assumes the `$database` global variable in your config is missing or empty. |
-| `SS_DATABASE_CHOOSE_NAME`| Boolean/Int. If set, then the system will choose a default database name for you if one isn't give in the $database variable. The database name will be "SS_" followed by the name of the folder into which you have installed SilverStripe. If this is enabled, it means that the phpinstaller will work out of the box without the installer needing to alter any files. This helps prevent accidental changes to the environment. If `SS_DATABASE_CHOOSE_NAME` is an integer greater than one, then an ancestor folder will be used for the database name. This is handy for a site that's hosted from /sites/examplesite/www or /buildbot/allmodules-2.3/build. If it's 2, the parent folder will be chosen; if it's 3 the grandparent, and so on.|
+| `SS_DATABASE_CHOOSE_NAME`| Boolean/Int. If defined, then the system will choose a default database name for you if one isn't give in the $database variable. The database name will be "SS_" followed by the name of the folder into which you have installed SilverStripe. If this is enabled, it means that the phpinstaller will work out of the box without the installer needing to alter any files. This helps prevent accidental changes to the environment. If `SS_DATABASE_CHOOSE_NAME` is an integer greater than one, then an ancestor folder will be used for the database name. This is handy for a site that's hosted from /sites/examplesite/www or /buildbot/allmodules-2.3/build. If it's 2, the parent folder will be chosen; if it's 3 the grandparent, and so on.|
| `SS_ENVIRONMENT_TYPE`| The environment type: dev, test or live.|
| `SS_DEFAULT_ADMIN_USERNAME`| The username of the default admin. This is a user with administrative privileges.|
| `SS_DEFAULT_ADMIN_PASSWORD`| The password of the default admin. This will not be stored in the database.|
| `SS_USE_BASIC_AUTH`| Protect the site with basic auth (good for test sites).
When using CGI/FastCGI with Apache, you will have to add the `RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]` rewrite rule to your `.htaccess` file|
-| `SS_SEND_ALL_EMAILS_TO`| If you set this define, all emails will be redirected to this address.|
-| `SS_SEND_ALL_EMAILS_FROM`| If you set this define, all emails will be send from this address.|
-| `SS_ERROR_LOG` | Relative path to the log file |
+| `SS_SEND_ALL_EMAILS_TO`| If you define this constant, all emails will be redirected to this address.|
+| `SS_SEND_ALL_EMAILS_FROM`| If you define this constant, all emails will be sent from this address.|
+| `SS_ERROR_LOG` | Relative path to the log file. |
diff --git a/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md b/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md
index 499dea2be..36b484a3c 100644
--- a/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md
+++ b/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md
@@ -512,7 +512,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit
<% loop $Children %>
- $Photo.SetWidth(150)
+ $Photo.ScaleWidth(150)
$Content.FirstParagraph
Read more >>
@@ -521,7 +521,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit
-This template is very similar to the *ArticleHolder* template. The *SetWidth* method of the `[api:Image]` class
+This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the `[api:Image]` class
will resize the image before sending it to the browser. The resized image is cached, so the server doesn't have to
resize the image every time the page is viewed.
@@ -537,13 +537,13 @@ The *StaffPage* template is also very straight forward.
$Title
- $Photo.SetWidth(433)
+ $Photo.ScaleWidth(433)
$Content
$Form
-Here we use the *SetWidth* method to get a different sized image from the same source image. You should now have
+Here we use the *ScaleWidth* method to get a different sized image from the same source image. You should now have
a complete staff section.
![](../_images/tutorial2_einstein.jpg)
diff --git a/docs/en/01_Tutorials/index.md b/docs/en/01_Tutorials/index.md
index de5afce94..8403c74e9 100644
--- a/docs/en/01_Tutorials/index.md
+++ b/docs/en/01_Tutorials/index.md
@@ -11,7 +11,7 @@ These include video screencasts, written tutorials and code examples to get you
* [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537)
* [Lesson 1: Creating your first theme](http://www.silverstripe.org/learn/lessons/creating-your-first-theme)
-* [Lesson 2: Migrating static templates into your theme]http://www.silverstripe.org/learn/lessons/migrating-static-templates-into-your-theme)
+* [Lesson 2: Migrating static templates into your theme](http://www.silverstripe.org/learn/lessons/migrating-static-templates-into-your-theme)
* [Lesson 3: Adding dynamic content](http://www.silverstripe.org/learn/lessons/adding-dynamic-content)
* [Lesson 4: Working with multiple templates](http://www.silverstripe.org/learn/lessons/working-with-multiple-templates)
* [Lesson 5: The holder/page pattern](http://www.silverstripe.org/learn/lessons/the-holderpage-pattern)
@@ -20,7 +20,13 @@ These include video screencasts, written tutorials and code examples to get you
* [Lesson 8: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/introduction-to-the-orm)
* [Lesson 9: Data Relationships - $has_many](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-has-many)
* [Lesson 10: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-many-many)
-
+* [Lesson 11: Introduction to frontend forms](http://www.silverstripe.org/learn/lessons/introduction-to-frontend-forms)
+* [Lesson 12: Data Extensions and SiteConfig](http://www.silverstripe.org/learn/lessons/data-extensions-and-siteconfig)
+* [Lesson 13: Introduction to ModelAdmin](http://www.silverstripe.org/learn/lessons/introduction-to-modeladmin)
+* [Lesson 14: Controller Actions/DataObjects as Pages](http://www.silverstripe.org/learn/lessons/controller-actions-dataobjects-as-pages)
+* [Lesson 15: Building a Search Form](http://www.silverstripe.org/learn/lessons/building-a-search-form)
+* [Lesson 16: Lists and Pagination](http://www.silverstripe.org/learn/lessons/lists-and-pagination)
+* [Lesson 17: Ajax Behaviour and Viewable Data](http://www.silverstripe.org/learn/lessons/ajax-behaviour-and-viewabledata)
## Help: If you get stuck
diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md
index 3ef4c9421..13b276dde 100644
--- a/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md
+++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md
@@ -31,6 +31,34 @@ functionality. It is usually added through the `[api:DataObject->getCMSFields()]
}
}
+### Specify which configuration to use
+
+By default, a config named 'cms' is used in any new `[api:HTMLEditorField]`.
+
+If you have created your own `[api:HtmlEditorConfig]` and would like to use it,
+you can call `HtmlEditorConfig::set_active('myConfig')` and all subsequently created `[api:HTMLEditorField]`
+will use the configuration with the name 'myConfig'.
+
+You can also specify which `[api:HtmlEditorConfig]` to use on a per field basis via the construct argument.
+This is particularly useful if you need different configurations for multiple `[api:HTMLEditorField]` on the same page or form.
+
+ :::php
+ class MyObject extends DataObject {
+ private static $db = array(
+ 'Content' => 'HTMLText',
+ 'OtherContent' => 'HTMLText'
+ );
+
+ public function getCMSFields() {
+ return new FieldList(array(
+ new HTMLEditorField('Content'),
+ new HTMLEditorField('OtherContent', 'Other content', $this->OtherContent, 'myConfig')
+ ));
+ }
+ }
+
+In the above example, the 'Content' field will use the default 'cms' config while 'OtherContent' will be using 'myConfig'.
+
## Configuration
To keep the JavaScript editor configuration manageable and extensible, we've wrapped it in a PHP class called
@@ -41,7 +69,6 @@ There can be multiple configs, which should always be created / accessed using `
then set the currently active config using `set_active()`.
-By default, a config named 'cms' is used in any field created throughout the CMS interface.
diff --git a/docs/en/02_Developer_Guides/05_Extending/How_Tos/03_Track_member_logins.md b/docs/en/02_Developer_Guides/05_Extending/How_Tos/03_Track_member_logins.md
index cf2eaa688..c3a00d601 100644
--- a/docs/en/02_Developer_Guides/05_Extending/How_Tos/03_Track_member_logins.md
+++ b/docs/en/02_Developer_Guides/05_Extending/How_Tos/03_Track_member_logins.md
@@ -37,7 +37,7 @@ explicitly logging in or by invoking the "remember me" functionality.
DB::query(sprintf(
'UPDATE "Member" SET "LastVisited" = %s, "NumVisit" = "NumVisit" + 1 WHERE "ID" = %d',
- DB::getConn()->now(),
+ DB::get_conn()->now(),
$this->owner->ID
));
}
diff --git a/docs/en/02_Developer_Guides/06_Testing/How_Tos/00_Write_a_SapphireTest.md b/docs/en/02_Developer_Guides/06_Testing/How_Tos/00_Write_a_SapphireTest.md
index ed8d69764..9bae22450 100644
--- a/docs/en/02_Developer_Guides/06_Testing/How_Tos/00_Write_a_SapphireTest.md
+++ b/docs/en/02_Developer_Guides/06_Testing/How_Tos/00_Write_a_SapphireTest.md
@@ -15,7 +15,7 @@ how you can load default records into the test database.
/**
* Defines the fixture file to use for this test class
*
- /
+ */
protected static $fixture_file = 'SiteTreeTest.yml';
/**
@@ -79,4 +79,4 @@ For more information on PHPUnit's assertions see the [PHPUnit manual](http://www
## API Documentation
* [api:SapphireTest]
-* [api:FunctionalTest]
\ No newline at end of file
+* [api:FunctionalTest]
diff --git a/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md b/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md
index 29bb57bd5..974791c93 100644
--- a/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md
+++ b/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md
@@ -27,7 +27,7 @@ come from user input.
Example:
:::php
- $records = DB::preparedQuery('SELECT * FROM "MyClass" WHERE "ID" = ?', array(3));
+ $records = DB::prepared_query('SELECT * FROM "MyClass" WHERE "ID" = ?', array(3));
$records = MyClass::get()->where(array('"ID" = ?' => 3));
$records = MyClass::get()->where(array('"ID"' => 3));
$records = DataObject::get_by_id('MyClass', 3);
@@ -48,7 +48,7 @@ Parameterised updates and inserts are also supported, but the syntax is a little
))
->assignSQL('"Created"', 'NOW()')
->execute();
- DB::preparedQuery(
+ DB::prepared_query(
'INSERT INTO "MyClass" ("Name", "Position", "Age", "Created") VALUES(?, ?, GREATEST(0,?,?), NOW())'
array('Daniel', 'Accountant', 24, 28)
);
@@ -100,7 +100,7 @@ and [datamodel](/developer_guides/model) for ways to parameterise, cast, and con
* `SQLQuery`
* `DB::query()`
-* `DB::preparedQuery()`
+* `DB::prepared_query()`
* `Director::urlParams()`
* `Controller->requestParams`, `Controller->urlParams`
* `SS_HTTPRequest` data
diff --git a/docs/en/02_Developer_Guides/14_Files/01_File_Management.md b/docs/en/02_Developer_Guides/14_Files/01_File_Management.md
new file mode 100644
index 000000000..f1d957a29
--- /dev/null
+++ b/docs/en/02_Developer_Guides/14_Files/01_File_Management.md
@@ -0,0 +1,40 @@
+summary: Learn how to work with File and Image records
+
+# File Management
+
+## Files, Images and Folders as database records
+
+All files, images and folders in the 'assets' directory are stored in the database. Each record has the following database fields:
+
+| Field name | Description |
+| ---------- | ----------- |
+| `ClassName` | The class name of the file (e.g. File, Image or Folder). |
+| `Name` | The 'basename' of the file, or the folder name. For example 'my-image.jpg', or 'images' for a folder. |
+| `Title` | The optional, human-readable title of the file for display only (doesn't apply to folders). |
+| `Filename` | The path to the file/folder, relative to the webroot. For example 'assets/images/my-image.jpg', or 'assets/images/' for a folder. |
+| `Content` | Typically unused, but handy for a textual representation of files. For example for fulltext indexing of PDF documents. |
+| `ShowInSearch` | Whether the file should be shown in search results, defaults to '1'. See ["Tutorial 4 - Site Search"](/tutorials/site_search) for enabling search. |
+| `ParentID` | The ID of the parent Folder that this File/Folder is in. A ParentID of '0' indicates that the File/Folder is in the 'assets' directory. |
+| `OwnerID` | The ID of the Member that 'owns' the File/Folder (not related to filesystem permissions). |
+
+## Management through the "Files" section of the CMS
+
+If you have the CMS module installed, you can manage files, folders and images in the "Files" section of the CMS. Inside this section, you will see a list of files and folders like below:
+
+![](../../_images/assets.png)
+
+You can click on any file to edit it, or click on any folder to open it. To delete a file or a folder, simply click the red 'X' symbol next to it. If you click to open a folder, you can go back up one level by clicking the 'up' arrow above the folder name (highlighted below):
+
+![](../../_images/assets_up.png)
+
+Once you click to edit a file, you will see a form similar to the one below, in which you can edit the file's title, filename, owner, or even change which folder the file is located in:
+
+![](../../_images/assets_editform.png)
+
+You may also notice the 'Sync files' button (highlighted below). This button allows CMS users to 'synchronise' the database (remember, all files/folders are stored as database records) with the filesystem. This is particularly useful if someone has uploaded or removed files/folders via FTP, for example.
+
+![](../../_images/assets_sync.png)
+
+## Upload
+
+Files can be managed through a `FileField` or an `UploadField`. The `[api:FileField]` class provides a simple HTML input with a type of "file", whereas an `[api:UploadField]` provides a much more feature-rich field (including AJAX-based uploads, previews, relationship management and file data management). See [`Reference - UploadField`](/developer_guides/forms/field_types/uploadfield) for more information about how to use the `UploadField` class.
\ No newline at end of file
diff --git a/docs/en/02_Developer_Guides/14_Files/01_Image.md b/docs/en/02_Developer_Guides/14_Files/01_Image.md
deleted file mode 100644
index bd3b4537a..000000000
--- a/docs/en/02_Developer_Guides/14_Files/01_Image.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# Image
-
-## Introduction
-
-Represents an image object through the `[api:Image]` class, inheriting all base functionality from the `[api:File]` class with extra functionality including resizing.
-
-## Usage
-
-### Managing images through form fields
-
-Images can be uploaded like any other file, through `[api:FileField]`.
-More advanced usage is possible through `[api:UploadField]`,
-which provides thumbnails, a detail view of the image properties,
-and management of relationships to other DataObject instances.
-Allows upload of images through limiting file extensions with `setAllowedExtensions()`.
-
-### Inserting images into the WYSIWYG editor
-
-See [Topics: Rich Text Editing](/topics/rich-text-editing).
-
-### Resizing Images in PHP
-
-The following are methods defined on the GD class which you can call on Image Objects. Note to get the following to work
-you need to have GD2 support in your PHP installation and because these generate files you must have write access to
-your tmp folder.
-
- :::php
- // manipulation functions
- $image->resize(width,height); // Basic resize, just skews the image
- $image->resizeRatio(width,height) // Resizes an image with max width and height
- $image->paddedResize(width,height) // Adds padding after resizing to width or height.
- $image->croppedImage(width,height) // Crops the image from the centre, to given values.
- $image->resizeByHeight(height) // Maximum height the image resizes to, keeps proportion
- $image->resizeByWidth(width) // Maximum width the image resizes to, keeps proportion
- $image->greyscale(r,g,b) // alters image channels ===
-
- // values
- $image->getHeight() // Returns the height of the image.
- $image->getWidth() // Returns the width of the image
- $image->getOrienation() // Returns a class constant: ORIENTATION_SQUARE or ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE
-
-
-You can also create your own functions by extending the image class, for example
-
- :::php
- class MyImage extends Image {
- public function generateRotateClockwise(GD $gd) {
- return $gd->rotate(90);
- }
-
- public function generateRotateCounterClockwise(GD $gd) {
- return $gd->rotate(270);
- }
-
- public function clearResampledImages() {
- $files = glob(Director::baseFolder().'/'.$this->Parent()->Filename."_resampled/*-$this->Name");
- foreach($files as $file) {unlink($file);}
- }
-
- public function Landscape() {
- return $this->getWidth() > $this->getHeight();
- }
-
- public function Portrait() {
- return $this->getWidth() < $this->getHeight();
- }
-
- public function generatePaddedImageByWidth(GD $gd,$width=600,$color="fff"){
- return $gd->paddedResize($width, round($gd->getHeight()/($gd->getWidth()/$width),0),$color);
- }
-
- public function Exif(){
- //http://www.v-nessa.net/2010/08/02/using-php-to-extract-image-exif-data
- $image = $this->AbsoluteURL;
- $d=new ArrayList();
- $exif = exif_read_data($image, 0, true);
- foreach ($exif as $key => $section) {
- $a=new ArrayList();
- foreach ($section as $name => $val)
- $a->push(new ArrayData(array("Title"=>$name,"Content"=>$val)));
- $d->push(new ArrayData(array("Title"=>strtolower($key),"Content"=>$a)));
- }
- return $d;
- }
- }
-
-### Resizing in Templates
-
-You can call certain resize functions directly from the template, to use the inbuilt GD functions as the template parser
-supports these, for example SetWidth() or SetHeight().
-
-For output of an image tag with the image automatically resized to 80px width, you can use:
-
- :::php
- $Image.SetWidth(80) // returns a image 80px wide, ratio kept the same
- $Image.SetHeight(80) // returns a image 80px tall, ratio kept the same
- $Image.SetSize(80,80) // returns a 80x80px padded image
- $Image.SetRatioSize(80,80) // Returns an image scaled proportional, with its greatest diameter scaled to 80px
- $Image.CroppedImage(80,80) // Returns an 80x80 image cropped from the center.
- $Image.PaddedImage(80, 80, FFFFFF) // Returns an 80x80 image. Unused space is padded white. No crop. No stretching
- $Image.Width // returns width of image
- $Image.Height // returns height of image
- $Image.Orientation // returns Orientation
- $Image.Filename // returns filename
- $Image.URL // returns filename
-
-
-### Form Upload
-
-For usage on a website form, see `[api:FileField]`.
-If you want to upload images within the CMS, see `[api:UploadField]`.
-
-### Image Quality
-
-To adjust the quality of the generated images when they are resized add the following to your mysite/config/config.yml file:
-
- :::yml
- GDBackend:
- default_quality: 90
-
-The default value is 75.
-
-### Clearing Thumbnail Cache
-
-Images are (like all other Files) synchronized with the SilverStripe database.
-This syncing happens whenever you load the "Files & Images" interface,
-and whenever you upload or modify an Image through SilverStripe.
-
-If you encounter problems with images not appearing, or have mysteriously disappeared, you can try manually flushing the
-image cache.
-
- http://localhost/dev/tasks/FlushGeneratedImagesTask
-
-## API Documentation
-`[api:Image]`
diff --git a/docs/en/02_Developer_Guides/14_Files/02_Images.md b/docs/en/02_Developer_Guides/14_Files/02_Images.md
new file mode 100644
index 000000000..46babf387
--- /dev/null
+++ b/docs/en/02_Developer_Guides/14_Files/02_Images.md
@@ -0,0 +1,163 @@
+summary: Learn how to crop and resize images in templates and PHP code
+
+# Image
+
+Represents an image object through the `[api:Image]` class, inheriting all base functionality from the `[api:File]` class with extra functionality including resizing.
+
+## Usage
+
+### Managing images through form fields
+
+Images can be uploaded like any other file, through `[api:FileField]`.
+More advanced usage is possible through `[api:UploadField]`,
+which provides thumbnails, a detail view of the image properties,
+and management of relationships to other DataObject instances.
+Allows upload of images through limiting file extensions with `setAllowedExtensions()`.
+
+### Inserting images into the WYSIWYG editor
+
+See [Topics: Rich Text Editing](/topics/rich-text-editing).
+
+### Manipulating images in Templates
+
+You can manipulate images directly from templates to create images that are
+resized and cropped to suit your needs. This doesn't affect the original
+image or clutter the CMS with any additional files, and any images you create
+in this way are cached for later use. In most cases the pixel aspect ratios of
+images are preserved (meaning images are not stretched).
+
+![](../../_images/image-methods.jpg)
+
+Here are some examples, assuming the `$Image` object has dimensions of 200x100px:
+
+ :::ss
+ // Scaling functions
+ $Image.ScaleWidth(150) // Returns a 150x75px image
+ $Image.ScaleMaxWidth(100) // Returns a 100x50px image (like ScaleWidth but prevents up-sampling)
+ $Image.ScaleHeight(150) // Returns a 300x150px image (up-sampled. Try to avoid doing this)
+ $Image.ScaleMaxHeight(150) // Returns a 200x100px image (like ScaleHeight but prevents up-sampling)
+ $Image.Fit(300,300) // Returns an image that fits within a 300x300px boundary, resulting in a 300x150px image (up-sampled)
+ $Image.FitMax(300,300) // Returns a 200x100px image (like Fit but prevents up-sampling)
+
+ // Cropping functions
+ $Image.Fill(150,150) // Returns a 150x150px image resized and cropped to fill specified dimensions (up-sampled)
+ $Image.FillMax(150,150) // Returns a 100x100px image (like Fill but prevents up-sampling)
+ $Image.CropWidth(150) // Returns a 150x100px image (trims excess pixels off the x axis from the center)
+ $Image.CropHeight(50) // Returns a 200x50px image (trims excess pixels off the y axis from the center)
+
+ // Padding functions (add space around an image)
+ $Image.Pad(100,100) // Returns a 100x100px padded image, with white bars added at the top and bottom
+ $Image.Pad(100, 100, CCCCCC) // Same as above but with a grey background
+
+ // Metadata
+ $Image.Width // Returns width of image
+ $Image.Height // Returns height of image
+ $Image.Orientation // Returns Orientation
+ $Image.Title // Returns the friendly file name
+ $Image.Name // Returns the actual file name
+ $Image.FileName // Returns the actual file name including directory path from web root
+ $Image.Link // Returns relative URL path to image
+ $Image.AbsoluteLink // Returns absolute URL path to image
+
+Image methods are chainable. Example:
+
+ :::ss
+
+
+### Manipulating images in PHP
+
+The image manipulation functions can be used in your code with the same names, example: `$image->Fill(150,150)`.
+
+Some of the MetaData functions need to be prefixed with 'get', example `getHeight()`, `getOrientation()` etc.
+
+Please refer to the `[api:Image]` API documentation for specific functions.
+
+### Creating custom image functions
+
+You can also create your own functions by extending the image class, for example
+
+ :::php
+ class MyImage extends DataExtension {
+
+ public function Landscape() {
+ return $this->owner->getWidth() > $this->owner->getHeight();
+ }
+
+ public function Portrait() {
+ return $this->owner->getWidth() < $this->owner->getHeight();
+ }
+
+ public function PerfectSquare() {
+ return $this->owner->getFormattedImage('PerfectSquare');
+ }
+
+ public function generatePerfectSquare(Image_Backend $backend) {
+ return $backend->croppedResize(100,100);
+ }
+
+ public function Exif(){
+ //http://www.v-nessa.net/2010/08/02/using-php-to-extract-image-exif-data
+ $image = $this->owner->AbsoluteLink();
+ $d=new ArrayList();
+ $exif = exif_read_data($image, 0, true);
+ foreach ($exif as $key => $section) {
+ $a=new ArrayList();
+ foreach ($section as $name => $val)
+ $a->push(new ArrayData(array("Title"=>$name,"Content"=>$val)));
+ $d->push(new ArrayData(array("Title"=>strtolower($key),"Content"=>$a)));
+ }
+ return $d;
+ }
+ }
+
+ :::yml
+ Image:
+ extensions:
+ - MyImage
+
+### Form Upload
+
+For usage on a website form, see `[api:FileField]`.
+If you want to upload images within the CMS, see `[api:UploadField]`.
+
+### Image Quality
+
+To adjust the quality of the generated images when they are resized add the
+following to your mysite/config/config.yml file:
+
+ :::yml
+ GDBackend:
+ default_quality: 90
+ # or
+ ImagickBackend:
+ default_quality: 90
+
+The default value is 75.
+
+By default SilverStripe image functions will not resample an image if no
+cropping or resizing is taking place. You can tell SilverStripe to always to
+always produce resampled output by adding this to your
+mysite/config/config.yml file:
+
+ :::yml
+ Image:
+ force_resample: true
+
+If you are intending to resample images with SilverStripe it is good practice
+to upload high quality (minimal compression) images as these will produce
+better results when resampled. Very high resolution images may cause GD to
+crash so a good size for website images is around 2000px on the longest edge.
+
+### Clearing Thumbnail Cache
+
+Images are (like all other Files) synchronized with the SilverStripe database.
+This syncing happens whenever you load the "Files & Images" interface,
+and whenever you upload or modify an Image through SilverStripe.
+
+If you encounter problems with images not appearing, or have mysteriously
+disappeared, you can try manually flushing the image cache.
+
+ http://localhost/dev/tasks/FlushGeneratedImagesTask
+
+## API Documentation
+`[api:Image]`
diff --git a/docs/en/02_Developer_Guides/14_Files/index.md b/docs/en/02_Developer_Guides/14_Files/index.md
index daff8a2cb..4c2c14c9b 100644
--- a/docs/en/02_Developer_Guides/14_Files/index.md
+++ b/docs/en/02_Developer_Guides/14_Files/index.md
@@ -1,40 +1,11 @@
-summary: Learn how to deal with File and Image records
+title: Files
+summary: Upload, manage and manipulate files and images.
+introduction: Upload, manage and manipulate files and images.
-# Files, Images and Folders
+[CHILDREN]
-## Files, Images and Folders as database records
+## API Documentation
-All files, images and folders in the 'assets' directory are stored in the database. Each record has the following database fields:
-
-| Field name | Description |
-| ---------- | ----------- |
-| `ClassName` | The class name of the file (e.g. File, Image or Folder). |
-| `Name` | The 'basename' of the file, or the folder name. For example 'my-image.jpg', or 'images' for a folder. |
-| `Title` | The optional, human-readable title of the file for display only (doesn't apply to folders). |
-| `Filename` | The path to the file/folder, relative to the webroot. For example 'assets/images/my-image.jpg', or 'assets/images/' for a folder. |
-| `Content` | Typically unused, but handy for a textual representation of files. For example for fulltext indexing of PDF documents. |
-| `ShowInSearch` | Whether the file should be shown in search results, defaults to '1'. See ["Tutorial 4 - Site Search"](/tutorials/site_search) for enabling search. |
-| `ParentID` | The ID of the parent Folder that this File/Folder is in. A ParentID of '0' indicates that the File/Folder is in the 'assets' directory. |
-| `OwnerID` | The ID of the Member that 'owns' the File/Folder (not related to filesystem permissions). |
-
-## Management through the "Files" section of the CMS
-
-If you have the CMS module installed, you can manage files, folders and images in the "Files" section of the CMS. Inside this section, you will see a list of files and folders like below:
-
-![](../../_images/assets.png)
-
-You can click on any file to edit it, or click on any folder to open it. To delete a file or a folder, simply click the red 'X' symbol next to it. If you click to open a folder, you can go back up one level by clicking the 'up' arrow above the folder name (highlighted below):
-
-![](../../_images/assets_up.png)
-
-Once you click to edit a file, you will see a form similar to the one below, in which you can edit the file's title, filename, owner, or even change which folder the file is located in:
-
-![](../../_images/assets_editform.png)
-
-You may also notice the 'Sync files' button (highlighted below). This button allows CMS users to 'synchronise' the database (remember, all files/folders are stored as database records) with the filesystem. This is particularly useful if someone has uploaded or removed files/folders via FTP, for example.
-
-![](../../_images/assets_sync.png)
-
-## Upload
-
-Files can be managed through a `FileField` or an `UploadField`. The `[api:FileField]` class provides a simple HTML input with a type of "file", whereas an `[api:UploadField]` provides a much more feature-rich field (including AJAX-based uploads, previews, relationship management and file data management). See [`Reference - UploadField`](/developer_guides/forms/field_types/uploadfield) for more information about how to use the `UploadField` class.
\ No newline at end of file
+* [api:File]
+* [api:Image]
+* [api:Folder]
\ No newline at end of file
diff --git a/docs/en/02_Developer_Guides/16_Execution_Pipeline/index.md b/docs/en/02_Developer_Guides/16_Execution_Pipeline/index.md
index b9b7ea418..8d8ca4b6f 100644
--- a/docs/en/02_Developer_Guides/16_Execution_Pipeline/index.md
+++ b/docs/en/02_Developer_Guides/16_Execution_Pipeline/index.md
@@ -11,7 +11,7 @@ SilverStripe needs to boot its core and run through several stages of processing
The first step in most environments is a rewrite of a request path into parameters passed to a PHP script.
This allows writing friendly URLs instead of linking directly to PHP files.
-The implementation depends on your web server, we'll show you the most common one here:
+The implementation depends on your web server; we'll show you the most common one here:
Apache with [mod_rewrite](http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html).
Check our [installation guides](/getting_started/installation) on how other web servers like IIS or nginx handle rewriting.
@@ -123,7 +123,7 @@ further filtering before content is sent to the end user
The framework provides the ability to hook into the request both before and
after it is handled to allow binding custom logic. This can be used
-to transform or filter request data, instanciate helpers, execute global logic,
+to transform or filter request data, instantiate helpers, execute global logic,
or even short-circuit execution (e.g. to enforce custom authentication schemes).
The ["Request Filters" documentation](../controllers/requestfilters) shows you how.
diff --git a/docs/en/04_Changelogs/3.2.0.md b/docs/en/04_Changelogs/3.2.0.md
index a03bb09b0..53f84e11d 100644
--- a/docs/en/04_Changelogs/3.2.0.md
+++ b/docs/en/04_Changelogs/3.2.0.md
@@ -1,80 +1,212 @@
-# 3.2.0 (unreleased)
+# 3.2.0
-## Overview
+## Contents
-### Framework
+* [Major Changes](#major-changes)
+* [Removed API](#deprecated-classesmethods-removed)
+* [New API](#new-and-changed-api)
+* [Bugfixes](#bugfixes)
+* [Upgrading Notes](#upgrading-notes)
- * Minimum PHP version raised to 5.3.3
- * `DataObject::validate()` method visibility changed to public
- * `NumericField` now uses HTML5 "number" type instead of "text"
- * `UploadField` "Select from files" shows files in all folders by default
- * `UploadField` won't display an overwrite warning unless `Upload::replaceFile` is true
- * `HtmlEditorField` no longer substitutes `
` for indented text
- * `ClassInfo::dataClassesFor` now returns classes which should have tables, regardless of whether those
- tables actually exist.
- * `SS_Filterable`, `SS_Limitable` and `SS_Sortable` now explicitly extend `SS_List`
- * `Convert::html2raw` no longer wraps text by default and can decode single quotes.
- * `Mailer` no longer calls `xml2raw` on all email subject line, and now must be passed in via plain text.
- * `ErrorControlChain` now supports reload on exceptions
- * `FormField::validate` now requires an instance of `Validator`
- * Implementation of new "Archive" concept for page removal, which supercedes "delete". Where deletion removed
- pages only from draft, archiving removes from both draft and live simultaneously.
+## Major changes
-#### Deprecated classes/methods removed
+* Minimum PHP version raised to 5.3.3
+* Introduction of new parameterised ORM
+* Default support for PDO
+* Moved SS_Report and ReportAdmin out to a separate module. If you're using
+ composer or downloading a release, this module should be included for you.
+ Otherwise, you'll need to include the module yourself
+ (https://github.com/silverstripe-labs/silverstripe-reports)
+* Moved SiteConfig also out to its own module. This will be included by
+ default if you include the CMS module.
+ (https://github.com/silverstripe/silverstripe-siteconfig)
+* Implementation of new "Archive" concept for page removal, which supercedes
+ "delete from draft". Where deletion removed pages only from draft, archiving
+ removes from both draft and live simultaneously.
+* Most of the `Image` manipulation methods have been renamed
- * `ToggleField` was deprecated in 3.1, and has been removed. Use custom Javascript with `ReadonlyField` instead.
- * `ExactMatchMultiFilter` was deprecated in 3.1, and has been removed. Use `ExactMatchFilter` instead.
- * `NegationFilter` was deprecated in 3.1, and has been removed. Use `ExactMatchFilter:not` instead.
- * `StartsWithMultiFilter` was deprecated in 3.1, and has been removed. Use `StartsWithFilter` instead.
- * `ScheduledTask` and subclasses like `DailyTask` were deprecated in 3.1, and have been removed.
+## Deprecated classes/methods removed
+
+* `ToggleField` was deprecated in 3.1, and has been removed. Use custom Javascript with `ReadonlyField` instead.
+* `ExactMatchMultiFilter` was deprecated in 3.1, and has been removed. Use `ExactMatchFilter` instead.
+* `NegationFilter` was deprecated in 3.1, and has been removed. Use `ExactMatchFilter:not` instead.
+* `StartsWithMultiFilter` was deprecated in 3.1, and has been removed. Use `StartsWithFilter` instead.
+* `ScheduledTask` and subclasses like `DailyTask` were deprecated in 3.1, and have been removed.
Use custom code instead, or a module like silverstripe-crontask: https://github.com/silverstripe-labs/silverstripe-crontask
- * `Cookie::forceExpiry()` was removed. Use `Cookie::force_expiry()` instead
- * `Object` statics removal: `get_static()`, `set_static()`, `uninherited_static()`, `combined_static()`,
+* `Cookie::forceExpiry()` was removed. Use `Cookie::force_expiry()` instead
+* `Object` statics removal: `get_static()`, `set_static()`, `uninherited_static()`, `combined_static()`,
`addStaticVars()` and `add_static_var()` removed. Use the Config methods instead.
- * `GD` methods removed: `setGD()`, `getGD()`, `hasGD()`. Use `setImageResource()`, `getImageResource()`, and `hasImageResource()` instead
- * `DataExtension::get_extra_config()` removed, no longer supports `extraStatics` or `extraDBFields`. Define your
+* `GD` methods removed: `setGD()`, `getGD()`, `hasGD()`. Use `setImageResource()`, `getImageResource()`, and `hasImageResource()` instead
+* `DataExtension::get_extra_config()` removed, no longer supports `extraStatics` or `extraDBFields`. Define your
statics on the class directly.
- * `DataList::getRange()` removed. Use `limit()` instead.
- * `SQLMap` removed. Call `map()` on a `DataList` or use `SS_Map` directly instead.
- * `Profiler` removed. Use xhprof or xdebug for profiling instead.
- * `Aggregate` removed. Call aggregate methods on a `DataList` instead e.g. `Member::get()->max('LastEdited')`
- * `MySQLDatabase::set_connection_charset()` removed. Use `MySQLDatabase.connection_charset` config setting instead
- * `SQLConditionalExpression/SQLQuery` `select()`, `limit()`, `orderby()`, `groupby()`, `having()`, `from()`, `leftjoin()`, `innerjoin()`, `where()` and `whereAny()` removed.
+* `DataList::getRange()` removed. Use `limit()` instead.
+* `SQLMap` removed. Call `map()` on a `DataList` or use `SS_Map` directly instead.
+* `Profiler` removed. Use xhprof or xdebug for profiling instead.
+* `Aggregate` removed. Call aggregate methods on a `DataList` instead e.g. `Member::get()->max('LastEdited')`
+* `MySQLDatabase::set_connection_charset()` removed. Use `MySQLDatabase.connection_charset` config setting instead
+* `SQLConditionalExpression/SQLQuery` `select()`, `limit()`, `orderby()`, `groupby()`, `having()`, `from()`, `leftjoin()`, `innerjoin()`, `where()` and `whereAny()` removed.
Use `set*()` and `add*()` methods instead.
- * Template `<% control $MyList %>` syntax removed. Use `<% loop $MyList %>` instead.
- * Object::singleton() method for better type-friendly singleton generation
+* Template `<% control $MyList %>` syntax removed. Use `<% loop $MyList %>` instead.
+* Removed `Member.LastVisited` and `Member.NumVisits` properties, see
+ [Howto: Track Member Logins](/extending/how_tos/track_member_logins) to restore functionality as custom code
-### CMS
+## New and changed API
- * `SearchForm::getSearchQuery` no longer pre-escapes search keywords and must be cast in your template
+* Implementation of a parameterised query framework eliminating the need to manually escape variables for
+ use in SQL queries. This has been integrated into nearly every level of the database ORM.
+* Refactor of database connectivity classes into separate components linked together through dependency injection
+* Refactor of `SQLQuery` into separate objects for each query type: `SQLQuery`, `SQLDelete`, `SQLUpdate` and `SQLInsert`
+* PDO is now a standard connector, and is available for all database interfaces
+* `DataObject::doValidate()` method visibility added to access `DataObject::validate` externally
+* `NumericField` now uses HTML5 "number" type instead of "text"
+* `UploadField` "Select from files" shows files in all folders by default
+* `UploadField` won't display an overwrite warning unless `Upload::replaceFile` is true
+* `HtmlEditorField` no longer substitutes `
` for indented text
+* `ClassInfo::dataClassesFor` now returns classes which should have tables, regardless of whether those
+ tables actually exist.
+* `SS_Filterable`, `SS_Limitable` and `SS_Sortable` now explicitly extend `SS_List`
+* `Convert::html2raw` no longer wraps text by default and can decode single quotes.
+* `Mailer` no longer calls `xml2raw` on all email subject line, and now must be passed in via plain text.
+* `ErrorControlChain` now supports reload on exceptions
+* `FormField::validate` now requires an instance of `Validator`
+* API: Removed URL routing by controller name
+* Security: The multiple authenticator login page should now be styled manually - i.e. without the default jQuery
+ UI layout. A new template, Security_MultiAuthenticatorLogin.ss is available.
+* Security: This controller's templates can be customised by overriding the `getTemplatesFor` function.
+* API: Form and FormField ID attributes rewritten.
+* `SearchForm::getSearchQuery` no longer pre-escapes search keywords and must
+ be cast in your template
+* Helper function `DB::placeholders` can be used to generate a comma separated list of placeholders
+ useful for creating "WHERE ... IN (?,...)" SQL fragments
+* Implemented Convert::symbol2sql to safely encode database and table names and identifiers.
+ E.g. `Convert::symbol2sql('table.column') => '"table"."column"';`
+* `Convert::raw2sql` may now quote the escaped value, as well as safely escape it, according to the current
+ database adaptor's preference.
+* `DB` class has been updated and many static methods have been renamed to conform to coding convention.
+ * Renamed API:
+ * `affectedRows` -> `affected_rows`
+ * `checkAndRepairTable` -> `check_and_repair_table`
+ * `createDatabase` -> `create_database`
+ * `createField` -> `create_field`
+ * `createTable` -> `create_table`
+ * `dontRequireField` -> `dont_require_field`
+ * `dontRequireTable` -> `dont_require_table`
+ * `fieldList` -> `field_list`
+ * `getConn` -> `get_conn`
+ * `getGeneratedID` -> `get_generated_id`
+ * `isActive` -> `is_active`
+ * `requireField` -> `require_field`
+ * `requireIndex` -> `require_index`
+ * `requireTable` -> `require_table`
+ * `setConn` -> `set_conn`
+ * `tableList` -> `table_list`
+ * Deprecated API:
+ * `getConnect` (Was placeholder for PDO connection string building code, but is made
+ redundant after the PDOConnector being fully abstracted)
+ * New API:
+ * `build_sql` - Hook into new SQL generation code
+ * `get_connector` (Nothing to do with getConnect)
+ * `get_schema`
+ * `placeholders`
+ * `prepared_query`
+* `SS_Database` class has been updated and many functions have been deprecated, or refactored into
+ the various other database classes. Most of the database management classes remain in the database
+ controller, due to individual databases (changing, creating of, etc) varying quite a lot from
+ API to API, but schema updates within a database itself is managed by an attached DBSchemaManager
+ * Refactored into DBSchemaManager:
+ * `createTable`
+ * `alterTable`
+ * `renameTable`
+ * `createField`
+ * `renameField`
+ * `fieldList`
+ * `tableList`
+ * `hasTable`
+ * `enumValuesForField`
+ * `beginSchemaUpdate` and `endSchemaUpdate` -> Use `schemaUpdate` with a callback
+ * `cancelSchemaUpdate`
+ * `isSchemaUpdating`
+ * `doesSchemaNeedUpdating`
+ * `transCreateTable`
+ * `transAlterTable`
+ * `transCreateField`
+ * `transCreateField`
+ * `transCreateIndex`
+ * `transAlterField`
+ * `transAlterIndex`
+ * `requireTable`
+ * `dontRequireTable`
+ * `requireIndex`
+ * `hasField`
+ * `requireField`
+ * `dontRequireField`
+ * Refactored into DBQueryBuilder
+ * `sqlQueryToString`
+ * Deprecated:
+ * `getConnect` - Was intended for use with PDO, but was never implemented, and is now
+ redundant, now that there is a stand-alone `PDOConnector`
+ * `prepStringForDB` - Use `quoteString` instead
+ * `dropDatabase` - Use `dropSelectedDatabase`
+ * `createDatabase` - Use `selectDatabase` with the second parameter set to true instead
+ * `allDatabaseNames` - Use `databaseList` instead
+ * `currentDatabase` - Use `getSelectedDatabase` instead
+ * `addslashes` - Use `escapeString` instead
+* `LogErrorEmailFormatter` now better displays SQL queries in errors by respecting line breaks
+* Installer has been majorly upgraded to handle the new database configuration options
+ and additional PDO functionality.
+* Created `SS_DatabaseException` to emit database errors. Query information such as SQL
+ and any relevant parameters may be used by error handling user code that catches
+ this exception.
+* The `SQLConditionGroup` interface has been created to represent dynamically
+ evaluated SQL conditions. This may be used to wrap a class that generates
+ a custom SQL clause(s) to be evaluated at the time of execution.
+* `DataObject` constants CHANGE_NONE, CHANGE_STRICT, and CHANGE_VALUE have been created
+ to provide more verbosity to field modification detection. This replaces the use of
+ various magic numbers with the same meaning.
+* create_table_options now uses constants as API specific filters rather than strings.
+ This is in order to promote better referencing of elements across the codebase.
+ See `FulltextSearchable->enable` for example.
+* `$FromEnd` iterator variable now available in templates.
+* Support for multiple HtmlEditorConfigs on the same page.
+* Object::singleton() method for better type-friendly singleton generation
+* New `Image` methods `CropWidth` and `CropHeight` added
+* 'Max' versions of `Image` methods introduced to prevent up-sampling
+* Update Image method names in PHP code and templates
+ * `SetRatioSize` -> `Fit`
+ * `CroppedImage` -> `Fill`
+ * `PaddedImage` -> `Pad`
+ * `SetSize` -> `Pad`
+ * `SetWidth` -> `ScaleWidth`
+ * `SetHeight` -> `ScaleHeight`
-## Changelog
+## Bugfixes
-### CMS
+* Reduced database regeneration chances on subsequent rebuilds after the initial dev/build
+* Elimination of various SQL injection vulnerability points
+* `DataObject::writeComponents()` now called correctly during `DataObject::write()`
+* Fixed missing theme declaration in installer
+* Fixed incorrect use of non-existing exception classes (e.g. `HTTPResponse_exception`)
+* `GridState` fixed to distinguish between check for missing values, and creation of
+ nested state values, in order to prevent non-empty values being returned for
+ missing keys. This was breaking `DataObject::get_by_id` by passing in an object
+ for the ID.
+* Fixed order of `File` fulltext searchable fields to use same order as actual fields.
+ This is required to prevent unnecessary rebuild of MS SQL databases when fulltext
+ searching is enabled.
+* In the past E_RECOVERABLE_ERROR would be ignored, and now correctly appear as warnings.
-### DataObject::validate() method visibility changed to public
-
-The visibility of `DataObject::validate()` has been changed from `protected` to `public`.
-
-Any existing classes that currently set this as `protected` should be changed like in
-this example:
-
- ::php
- class MyDataClass extends DataObject {
- ...
- public function validate() {
- ...
- }
- ...
- }
+## Upgrading Notes
### UploadField "Select from files" shows files in all folders by default
In order to list files in a single folder by default (previous default behaviour),
use `setDisplayFolderName()` with a folder path relative to `assets/`:
+
+ :::php
UploadField::create('MyField')->setDisplayFolderName('Uploads');
+
### UploadField won't display an overwrite warning unless Upload:replaceFile is true
The configuration setting `UploadField:overwriteWarning` is dependent on `Upload:replaceFile`
@@ -84,6 +216,7 @@ To display a warning before overwriting a file:
Via config:
+
::yaml
Upload:
# Replace an existing file rather than renaming the new one.
@@ -92,12 +225,15 @@ Via config:
# Warning before overwriting existing file (only relevant when Upload: replaceFile is true)
overwriteWarning: true
+
Or per instance:
+
::php
$uploadField->getUpload()->setReplaceFile(true);
$uploadField->setOverwriteWarning(true);
+
### File.allowed_extensions restrictions
Certain file types such as swf, html, htm, xhtml and xml have been removed from the list
@@ -126,35 +262,160 @@ languages like JavaScript won't be able to read them.
To set it back to be non-HTTP only, you need to set the `$httpOnly` argument to false when calling
`Cookie::set()`.
-### Bugfixes
- * Migration of code to use new parameterised framework
+### API: Removed URL routing by controller name
-### Framework
+The auto-routing of controller class names to URL endpoints
+has been removed (rule: `'$Controller//$Action/$ID/$OtherID': '*'`).
+This increases clarity in routing since it makes URL entpoints explicit,
+and thereby simplifies system and security reviews.
- * Implementation of a parameterised query framework eliminating the need to manually escape variables for
- use in SQL queries. This has been integrated into nearly every level of the database ORM.
- * Refactor of database connectivity classes into separate components linked together through dependency injection
- * Refactor of `SQLQuery` into separate objects for each query type: `SQLQuery`, `SQLDelete`, `SQLUpdate` and `SQLInsert`
- * Rename of API methods to conform to coding conventions
- * PDO is now a standard connector, and is available for all database interfaces
- * Additional database and query generation tools
+Please access any custom controllers exclusively through self-defined
+[routes](/reference/director). For controllers extending `Page_Controller`,
+simply use the provided page URLs.
-## Bugfixes
- * Reduced database regeneration chances on subsequent rebuilds after the initial dev/build
- * Elimination of various SQL injection vulnerability points
- * `DataObject::writeComponents()` now called correctly during `DataObject::write()`
- * Fixed missing theme declaration in installer
- * Fixed incorrect use of non-existing exception classes (e.g. `HTTPResponse_exception`)
- * `GridState` fixed to distinguish between check for missing values, and creation of
- nested state values, in order to prevent non-empty values being returned for
- missing keys. This was breaking `DataObject::get_by_id` by passing in an object
- for the ID.
- * Fixed order of `File` fulltext searchable fields to use same order as actual fields.
- This is required to prevent unnecessary rebuild of MS SQL databases when fulltext
- searching is enabled.
+ :::php
+ class MyController extends Controller {
+ static $allowed_actions = array('myaction');
+ public function myaction($request) {
+ // ...
+ }
+ }
+
+
+Create a new file `mysite/_config/routes.yml`
+(read more about the [config format](/topics/configuration)).
+Your controller is now available on `http://yourdomain.com/my-controller-endpoint`,
+after refreshing the configuration cache through `?flush=all`.
+
+
+ :::yaml
+ ---
+ Name: my-routes
+ After: framework/routes#coreroutes
+ ---
+ Director:
+ rules:
+ 'my-controller-endpoint//$Action' : 'MyController'
+
+
+The auto-routing is still in place for unit tests,
+since its a frequently used feature there. Although we advise against it,
+you can reinstate the old behaviour through a director rule:
+
+
+ :::yaml
+ ---
+ Name: my-routes
+ After: framework/routes#coreroutes
+ ---
+ Director:
+ rules:
+ '$Controller//$Action/$ID/$OtherID': '*'
+
+
+### API: Default Form and FormField ID attributes rewritten.
+
+Previously the automatic generation of ID attributes throughout the Form API
+could generate invalid ID values such as Password[ConfirmedPassword] as well
+as duplicate ID values between forms on the same page. For example, if you
+created a field called `Email` on more than one form on the page, the resulting
+HTML would have multiple instances of `#Email`. ID should be a unique
+identifier for a single element within the document.
+
+This rewrite has several angles, each of which is described below. If you rely
+on ID values in your CSS files, Javascript code or application unit tests *you
+will need to update your code*.
+
+#### Conversion of invalid form ID values
+
+ID attributes on Form and Form Fields will now follow the
+[HTML specification](http://www.w3.org/TR/REC-html40/types.html#type-cdata).
+Generating ID attributes is now handled by the new `FormTemplateHelper` class.
+
+Please test each of your existing site forms to ensure that they work
+correctly in particular, javascript and css styles which rely on specific ID
+values.
+
+#### Invalid ID attributes stripped
+
+ID attributes will now be run through `Convert::raw2htmlid`. Invalid characters
+are replaced with a single underscore character. Duplicate, leading and trailing
+underscores are removed. Custom ID attributes (set through `setHTMLID`) will not
+be altered.
+
+Before:
+
+
+ :::html
+