From cd4ebb25ca59b15e216ce2e546b7c5cf2e6b5e51 Mon Sep 17 00:00:00 2001
From: James Goodman
Date: Thu, 10 Oct 2013 08:04:03 +0000
Subject: [PATCH 001/119] Fix HTMLText AbsoluteLinks not parsing placeholder
Add an AbsoluteLinks method to HTMLText that parses placeholders instead of returning the raw value.
---
model/fieldtypes/HTMLText.php | 12 ++++++++--
tests/api/RSSFeedTest.php | 45 ++++++++++++++++++++++++++++++++---
2 files changed, 52 insertions(+), 5 deletions(-)
diff --git a/model/fieldtypes/HTMLText.php b/model/fieldtypes/HTMLText.php
index 46d462aa8..b2c8b1885 100644
--- a/model/fieldtypes/HTMLText.php
+++ b/model/fieldtypes/HTMLText.php
@@ -172,8 +172,16 @@ class HTMLText extends Text {
/* If we didn't find a sentence ending, use the summary. We re-call rather than using paragraph so that
* Summary will limit the result this time */
return $this->Summary();
- }
-
+ }
+
+ /**
+ * Return the value of the field with relative links converted to absolute urls (with placeholders parsed).
+ * @return string
+ */
+ public function AbsoluteLinks() {
+ return HTTP::absoluteURLs($this->forTemplate());
+ }
+
public function forTemplate() {
if ($this->processShortcodes) {
return ShortcodeParser::get_active()->parse($this->value);
diff --git a/tests/api/RSSFeedTest.php b/tests/api/RSSFeedTest.php
index 57032965a..0402bd30a 100644
--- a/tests/api/RSSFeedTest.php
+++ b/tests/api/RSSFeedTest.php
@@ -16,7 +16,6 @@ class RSSFeedTest extends SapphireTest {
$rssFeed = new RSSFeed($list, "http://www.example.com", "Test RSS Feed", "Test RSS Feed Description");
$content = $rssFeed->outputToBrowser();
- //Debug::message($content);
$this->assertContains('http://www.example.org/item-a/', $content);
$this->assertContains('http://www.example.com/item-b.html', $content);
$this->assertContains('http://www.example.com/item-c.html', $content);
@@ -44,6 +43,23 @@ class RSSFeedTest extends SapphireTest {
$this->assertContains('ItemC AltContent', $content);
}
+ public function testRSSFeedWithShortcode() {
+ $list = new ArrayList();
+ $list->push(new RSSFeedTest_ItemD());
+
+ $rssFeed = new RSSFeed($list, "http://www.example.com", "Test RSS Feed", "Test RSS Feed Description");
+ $content = $rssFeed->outputToBrowser();
+
+ $this->assertContains('http://www.example.org/item-d.html', $content);
+
+ $this->assertContains('ItemD', $content);
+
+ $this->assertContains(
+ '<p>ItemD Content test shortcode output</p>',
+ $content
+ );
+ }
+
public function testRenderWithTemplate() {
$rssFeed = new RSSFeed(new ArrayList(), "", "", "");
$rssFeed->setTemplate('RSSFeedTest');
@@ -61,6 +77,10 @@ class RSSFeedTest extends SapphireTest {
Config::inst()->update('Director', 'alternate_base_url', '/');
if(!self::$original_host) self::$original_host = $_SERVER['HTTP_HOST'];
$_SERVER['HTTP_HOST'] = 'www.example.org';
+
+ ShortcodeParser::get('default')->register('test_shortcode', function() {
+ return 'test shortcode output';
+ });
}
public function tearDown() {
@@ -77,7 +97,7 @@ class RSSFeedTest_ItemA extends ViewableData {
'Content' => 'Text',
'AltContent' => 'Text',
);
-
+
public function getTitle() {
return "ItemA";
}
@@ -89,7 +109,7 @@ class RSSFeedTest_ItemA extends ViewableData {
public function getAltContent() {
return "ItemA AltContent";
}
-
+
public function Link($action = null) {
return Controller::join_links("item-a/", $action);
}
@@ -134,3 +154,22 @@ class RSSFeedTest_ItemC extends ViewableData {
return "http://www.example.com/item-c.html";
}
}
+
+class RSSFeedTest_ItemD extends ViewableData {
+ // ItemD test fields - all fields use casting but Content & AltContent cast as HTMLText
+ private static $casting = array(
+ 'Title' => 'Varchar',
+ 'Content' => 'HTMLText'
+ );
+
+ public $Title = 'ItemD';
+ public $Content = 'ItemD Content [test_shortcode]
';
+
+ public function Link() {
+ return 'item-d.html';
+ }
+
+ public function AbsoluteLink() {
+ return 'http://www.example.org/item-d.html';
+ }
+}
From 0096ab0d3d1ba07dffbc025eb1677b299180bad7 Mon Sep 17 00:00:00 2001
From: Devlin
Date: Tue, 4 Mar 2014 13:43:08 +0100
Subject: [PATCH 002/119] Upload: refactor file versioning
---
filesystem/Upload.php | 38 ++++++++++++++---------
tests/filesystem/UploadTest.php | 55 ++++++++++++++++++++++++++++++++-
2 files changed, 77 insertions(+), 16 deletions(-)
diff --git a/filesystem/Upload.php b/filesystem/Upload.php
index ec8b7ca12..6b0003b9c 100644
--- a/filesystem/Upload.php
+++ b/filesystem/Upload.php
@@ -134,9 +134,10 @@ class Upload extends Controller {
$file = $nameFilter->filter($tmpFile['name']);
$fileName = basename($file);
- $relativeFilePath = $parentFolder
- ? $parentFolder->getRelativePath() . "$fileName"
- : ASSETS_DIR . "/" . $fileName;
+ $relativeFolderPath = $parentFolder
+ ? $parentFolder->getRelativePath()
+ : ASSETS_DIR . '/';
+ $relativeFilePath = $relativeFolderPath . $fileName;
// Create a new file record (or try to retrieve an existing one)
if(!$this->file) {
@@ -156,26 +157,33 @@ class Upload extends Controller {
}
}
- // if filename already exists, version the filename (e.g. test.gif to test1.gif)
+ // if filename already exists, version the filename (e.g. test.gif to test2.gif, test2.gif to test3.gif)
if(!$this->replaceFile) {
+ $fileSuffixArray = explode('.', $fileName);
+ $fileTitle = array_shift($fileSuffixArray);
+ $fileSuffix = !empty($fileSuffixArray)
+ ? '.' . implode('.', $fileSuffixArray)
+ : null;
+
+ // make sure files retain valid extensions
+ $oldFilePath = $relativeFilePath;
+ $relativeFilePath = $relativeFolderPath . $fileTitle . $fileSuffix;
+ if($oldFilePath !== $relativeFilePath) {
+ user_error("Couldn't fix $relativeFilePath", E_USER_ERROR);
+ }
while(file_exists("$base/$relativeFilePath")) {
$i = isset($i) ? ($i+1) : 2;
- $oldFilePath = $relativeFilePath;
- // make sure archives retain valid extensions
- if(substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.gz')) == '.tar.gz' ||
- substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.bz2')) == '.tar.bz2') {
- $relativeFilePath = preg_replace('/[0-9]*(\.tar\.[^.]+$)/', $i . '\\1', $relativeFilePath);
- } else if (strpos($relativeFilePath, '.') !== false) {
- $relativeFilePath = preg_replace('/[0-9]*(\.[^.]+$)/', $i . '\\1', $relativeFilePath);
- } else if (strpos($relativeFilePath, '_') !== false) {
- $relativeFilePath = preg_replace('/_([^_]+$)/', '_'.$i, $relativeFilePath);
+ $pattern = '/([0-9]+$)/';
+ if(preg_match($pattern, $fileTitle)) {
+ $fileTitle = preg_replace($pattern, $i, $fileTitle);
} else {
- $relativeFilePath .= '_'.$i;
+ $fileTitle .= $i;
}
+ $relativeFilePath = $relativeFolderPath . $fileTitle . $fileSuffix;
if($oldFilePath == $relativeFilePath && $i > 2) {
user_error("Couldn't fix $relativeFilePath with $i tries", E_USER_ERROR);
}
- }
+ }
} else {
//reset the ownerID to the current member when replacing files
$this->file->OwnerID = (Member::currentUser() ? Member::currentUser()->ID : 0);
diff --git a/tests/filesystem/UploadTest.php b/tests/filesystem/UploadTest.php
index 48a833027..aa825948c 100644
--- a/tests/filesystem/UploadTest.php
+++ b/tests/filesystem/UploadTest.php
@@ -258,6 +258,10 @@ class UploadTest extends SapphireTest {
$file->Name,
'File has a name without a number because it\'s not a duplicate'
);
+ $this->assertFileExists(
+ BASE_PATH . '/' . $file->getRelativePath(),
+ 'File exists'
+ );
$u = new Upload();
$u->load($tmpFile);
@@ -267,9 +271,37 @@ class UploadTest extends SapphireTest {
$file2->Name,
'File receives a number attached to the end before the extension'
);
+ $this->assertFileExists(
+ BASE_PATH . '/' . $file2->getRelativePath(),
+ 'File exists'
+ );
+ $this->assertGreaterThan(
+ $file->ID,
+ $file2->ID,
+ 'File database record is not the same'
+ );
+
+ $u = new Upload();
+ $u->load($tmpFile);
+ $file3 = $u->getFile();
+ $this->assertEquals(
+ 'UploadTest-testUpload3.tar.gz',
+ $file3->Name,
+ 'File receives a number attached to the end before the extension'
+ );
+ $this->assertFileExists(
+ BASE_PATH . '/' . $file3->getRelativePath(),
+ 'File exists'
+ );
+ $this->assertGreaterThan(
+ $file2->ID,
+ $file3->ID,
+ 'File database record is not the same'
+ );
$file->delete();
$file2->delete();
+ $file3->delete();
}
public function testUploadFileWithNoExtensionTwiceAppendsNumber() {
@@ -307,16 +339,29 @@ class UploadTest extends SapphireTest {
$file->Name,
'File is uploaded without extension'
);
+ $this->assertFileExists(
+ BASE_PATH . '/' . $file->getRelativePath(),
+ 'File exists'
+ );
$u = new Upload();
$u->setValidator($v);
$u->load($tmpFile);
$file2 = $u->getFile();
$this->assertEquals(
- 'UploadTest-testUpload-2',
+ 'UploadTest-testUpload2',
$file2->Name,
'File receives a number attached to the end'
);
+ $this->assertFileExists(
+ BASE_PATH . '/' . $file2->getRelativePath(),
+ 'File exists'
+ );
+ $this->assertGreaterThan(
+ $file->ID,
+ $file2->ID,
+ 'File database record is not the same'
+ );
$file->delete();
$file2->delete();
@@ -357,6 +402,10 @@ class UploadTest extends SapphireTest {
$file->Name,
'File is uploaded without extension'
);
+ $this->assertFileExists(
+ BASE_PATH . '/' . $file->getRelativePath(),
+ 'File exists'
+ );
$u = new Upload();
$u->setValidator($v);
@@ -368,6 +417,10 @@ class UploadTest extends SapphireTest {
$file2->Name,
'File does not receive new name'
);
+ $this->assertFileExists(
+ BASE_PATH . '/' . $file2->getRelativePath(),
+ 'File exists'
+ );
$this->assertEquals(
$file->ID,
$file2->ID,
From 58f8c2944a19bb88ed139d7f78f2c63ab1145d02 Mon Sep 17 00:00:00 2001
From: Loz Calver
Date: Tue, 18 Feb 2014 22:20:18 +0000
Subject: [PATCH 003/119] Fix: Incorrect calculation of UploadField height
(fixes #2862)
---
css/AssetUploadField.css | 6 ++++--
javascript/UploadField.js | 28 ++++++++++------------------
scss/AssetUploadField.scss | 10 ++++++++--
3 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/css/AssetUploadField.css b/css/AssetUploadField.css
index 7fb70211d..991d55f28 100644
--- a/css/AssetUploadField.css
+++ b/css/AssetUploadField.css
@@ -19,10 +19,12 @@ Used in side panels and action tabs
#Form_EditorToolbarMediaForm .ui-tabs-panel { padding-left: 0px; }
-body.cms.ss-uploadfield-edit-iframe, .composite.ss-assetuploadfield .details fieldset { padding: 16px; overflow: auto; background: #E2E2E2; }
+body.cms.ss-uploadfield-edit-iframe, .composite.ss-assetuploadfield .details fieldset { overflow: auto; background: #E2E2E2; }
body.cms.ss-uploadfield-edit-iframe span.readonly, .composite.ss-assetuploadfield .details fieldset span.readonly { font-style: italic; color: #777777; text-shadow: 0px 1px 0px #fff; }
body.cms.ss-uploadfield-edit-iframe .fieldholder-small label, .composite.ss-assetuploadfield .details fieldset .fieldholder-small label { margin-left: 0; }
+.composite.ss-assetuploadfield .details fieldset { padding: 16px; }
+
.ss-assetuploadfield h3 { border-bottom: 1px solid rgba(201, 205, 206, 0.8); -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); margin: 0 0 8px; padding: 0 0 7px; clear: both; position: relative; }
.ss-assetuploadfield .field { border-bottom: 0; box-shadow: none; }
.ss-assetuploadfield .fileOverview { clear: both; margin-top: 10px; position: relative; }
@@ -69,7 +71,7 @@ body.cms.ss-uploadfield-edit-iframe .fieldholder-small label, .composite.ss-asse
.ss-assetuploadfield .ss-uploadfield-files .ss-uploadfield-item-editform { /* don't use display none, for it will break jQuery('iframe').contents().height() */ height: 0; overflow: hidden; clear: both; }
.ss-assetuploadfield .ss-uploadfield-files .ss-uploadfield-item-editform.loading { width: 100%; height: 22px; padding: 15px 0; background: url(../admin/images/spinner.gif) no-repeat 50% 50%; }
.ss-assetuploadfield .ss-uploadfield-files .ss-uploadfield-item-editform.loading iframe { /* Old IE needs this or it'll give the iframe a white background, covering the spinner */ padding-top: 0; margin-top: 37px; border: none; }
-.ss-assetuploadfield .ss-uploadfield-files .ss-uploadfield-item-editform iframe { width: 100%; }
+.ss-assetuploadfield .ss-uploadfield-files .ss-uploadfield-item-editform iframe { width: 100%; padding: 16px; background: #E2E2E2; }
.ss-assetuploadfield .ss-uploadfield-addfile .ss-uploadfield-item-info { float: left; margin: 25px 0 0; }
.ss-insert-media .ss-assetuploadfield .ss-uploadfield-addfile .ss-uploadfield-item-info { margin: 10px 0px 0 20px; }
.ss-assetuploadfield .ss-uploadfield-addfile .ss-uploadfield-item-info label { font-size: 18px; line-height: 30px; padding: 0px 16px; margin-right: 0px; }
diff --git a/javascript/UploadField.js b/javascript/UploadField.js
index 5f791306c..33e70de6c 100644
--- a/javascript/UploadField.js
+++ b/javascript/UploadField.js
@@ -489,27 +489,19 @@
$('div.ss-upload .ss-uploadfield-item-editform').entwine({
fitHeight: function() {
- var iframe = this.find('iframe'), padding = 32, parentPadding = 2;
- var h = iframe.contents().find('form').height() + padding;
+ var iframe = this.find('iframe'),
+ contents = iframe.contents().find('body'),
+ bodyH = contents.find('form').outerHeight(true), // We set the height to match the form's outer height
+ iframeH = bodyH + (iframe.outerHeight(true) - iframe.height()), // content's height + padding on iframe elem
+ containerH = iframeH + (this.outerHeight(true) - this.height()); // iframe height + padding on container elem
- if(this.hasClass('includeParent')){
- padding=0;
- parentPadding=12;
- }
-
- /* Set height of body except in IE8. Setting this in IE8 breaks the
- dropdown */
- if(!$.browser.msie && $.browser.version.slice(0,3) != "8.0"){
- iframe.contents().find('body').css({'height':(h-padding)});
+ /* Set height of body except in IE8. Setting this in IE8 breaks the dropdown */
+ if( ! $.browser.msie && $.browser.version.slice(0,3) != "8.0"){
+ contents.find('body').css({'height': bodyH});
}
- // Set iframe to match its contents height
- iframe.height(h);
-
- // set container to match the same height
- iframe.parent().animate({height: h+parentPadding}, 500);
- iframe.contents().find('body form').css({'width':'98%'});
-
+ iframe.height(iframeH);
+ this.animate({height: containerH}, 500);
},
toggleEditForm: function() {
var itemInfo = this.prev('.ss-uploadfield-item-info'), status = itemInfo.find('.ss-uploadfield-item-status');
diff --git a/scss/AssetUploadField.scss b/scss/AssetUploadField.scss
index fcc64af6d..0e8f78ddb 100644
--- a/scss/AssetUploadField.scss
+++ b/scss/AssetUploadField.scss
@@ -33,8 +33,7 @@
}
}
-body.cms.ss-uploadfield-edit-iframe, .composite.ss-assetuploadfield .details fieldset {
- padding: $grid-x*2;
+body.cms.ss-uploadfield-edit-iframe {
overflow: auto;
background: #E2E2E2;
@@ -51,6 +50,11 @@ body.cms.ss-uploadfield-edit-iframe, .composite.ss-assetuploadfield .details fie
}
}
+.composite.ss-assetuploadfield .details fieldset {
+ @extend body.cms.ss-uploadfield-edit-iframe;
+ padding: $grid-x*2;
+}
+
.ss-assetuploadfield {
h3 {
border-bottom: 1px solid $color-shadow-light;
@@ -278,6 +282,8 @@ body.cms.ss-uploadfield-edit-iframe, .composite.ss-assetuploadfield .details fie
iframe {
width: 100%;
+ padding: $grid-x*2;
+ background: #E2E2E2;
}
}
}
From a3fb47a929a6f15b5fe80a70bf9db2cd796642f8 Mon Sep 17 00:00:00 2001
From: Loz Calver
Date: Tue, 18 Feb 2014 22:24:35 +0000
Subject: [PATCH 004/119] Fix: Remove superfluous iframe resize for UploadField
(fixes #2863)
---
javascript/UploadField.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/javascript/UploadField.js b/javascript/UploadField.js
index 33e70de6c..04095c4fc 100644
--- a/javascript/UploadField.js
+++ b/javascript/UploadField.js
@@ -439,8 +439,6 @@
self._prepareIframe(iframe, editform, itemInfo);
iframe.data('src', '');
}
-
- if (editform.hasClass('opened')) editform.fitHeight();
});
} else {
self._prepareIframe(iframe, editform, itemInfo);
From b3d52de53111bc302d12f78f5cc75d04065f5d99 Mon Sep 17 00:00:00 2001
From: Igor Nadj
Date: Wed, 6 Aug 2014 16:29:52 +1200
Subject: [PATCH 005/119] ENH making /dev extendable
---
_config/dev.yml | 23 +++++
dev/DevBuildController.php | 32 +++++++
dev/DevelopmentAdmin.php | 126 ++++++++++++++++-----------
tests/dev/DevAdminControllerTest.php | 116 ++++++++++++++++++++++++
4 files changed, 244 insertions(+), 53 deletions(-)
create mode 100644 _config/dev.yml
create mode 100644 dev/DevBuildController.php
create mode 100644 tests/dev/DevAdminControllerTest.php
diff --git a/_config/dev.yml b/_config/dev.yml
new file mode 100644
index 000000000..a3aa0b604
--- /dev/null
+++ b/_config/dev.yml
@@ -0,0 +1,23 @@
+---
+Name: DevelopmentAdmin
+---
+DevelopmentAdmin:
+ registered_controllers:
+ build:
+ controller: 'DevBuildController'
+ links:
+ build: 'Build/rebuild this environment. Call this whenever you have updated your project sources'
+ tests:
+ controller: 'TestRunner'
+ links:
+ tests: 'See a list of unit tests to run'
+ 'tests/all': 'Run all tests'
+ jstests:
+ controller: 'JSTestRunner'
+ links:
+ jstests: 'See a list of JavaScript tests to run'
+ 'jstests/all': 'Run all JavaScript tests'
+ tasks:
+ controller: 'TaskRunner'
+ links:
+ tasks: 'See a list of build tasks to run'
\ No newline at end of file
diff --git a/dev/DevBuildController.php b/dev/DevBuildController.php
new file mode 100644
index 000000000..02ffbaa52
--- /dev/null
+++ b/dev/DevBuildController.php
@@ -0,0 +1,32 @@
+ 'build'
+ );
+
+ private static $allowed_actions = array(
+ 'build'
+ );
+
+
+ public function build($request) {
+ if(Director::is_cli()) {
+ $da = DatabaseAdmin::create();
+ return $da->handleRequest($request, $this->model);
+ } else {
+ $renderer = DebugView::create();
+ $renderer->writeHeader();
+ $renderer->writeInfo("Environment Builder", Director::absoluteBaseURL());
+ echo "";
+
+ $da = DatabaseAdmin::create();
+ return $da->handleRequest($request, $this->model);
+
+ echo "
";
+ $renderer->writeFooter();
+ }
+ }
+
+}
diff --git a/dev/DevelopmentAdmin.php b/dev/DevelopmentAdmin.php
index 6533aea10..d68d941f7 100644
--- a/dev/DevelopmentAdmin.php
+++ b/dev/DevelopmentAdmin.php
@@ -1,10 +1,15 @@
'index',
'build/defaults' => 'buildDefaults',
- '$Action' => '$Action',
- '$Action//$Action/$ID' => 'handleAction',
+ 'generatesecuretoken' => 'generatesecuretoken',
+ '$Action' => 'runRegisteredController',
);
private static $allowed_actions = array(
'index',
- 'tests',
- 'jstests',
- 'tasks',
- 'viewmodel',
- 'build',
- 'reset',
- 'viewcode',
- 'generatesecuretoken',
'buildDefaults',
+ 'runRegisteredController',
+ 'generatesecuretoken',
);
+
public function init() {
parent::init();
@@ -80,20 +80,8 @@ class DevelopmentAdmin extends Controller {
}
public function index() {
- $actions = array(
- "build" => "Build/rebuild this environment. Call this whenever you have updated your project sources",
- "tests" => "See a list of unit tests to run",
- "tests/all" => "Run all tests",
- "jstests" => "See a list of JavaScript tests to run",
- "jstests/all" => "Run all JavaScript tests",
- "tasks" => "See a list of build tasks to run"
- );
-
// Web mode
if(!Director::is_cli()) {
- // This action is sake-only right now.
- unset($actions["modules/add"]);
-
$renderer = DebugView::create();
$renderer->writeHeader();
$renderer->writeInfo("SilverStripe Development Tools", Director::absoluteBaseURL());
@@ -101,7 +89,7 @@ class DevelopmentAdmin extends Controller {
echo '';
$evenOdd = "odd";
- foreach($actions as $action => $description) {
+ foreach(self::get_links() as $action => $description) {
echo "- /dev/$action:"
. " $description
\n";
$evenOdd = ($evenOdd == "odd") ? "even" : "odd";
@@ -113,42 +101,74 @@ class DevelopmentAdmin extends Controller {
} else {
echo "SILVERSTRIPE DEVELOPMENT TOOLS\n--------------------------\n\n";
echo "You can execute any of the following commands:\n\n";
- foreach($actions as $action => $description) {
+ foreach(self::get_links() as $action => $description) {
echo " sake dev/$action: $description\n";
}
echo "\n\n";
}
}
- public function tests($request) {
- return TestRunner::create();
- }
-
- public function jstests($request) {
- return JSTestRunner::create();
- }
-
- public function tasks() {
- return TaskRunner::create();
- }
-
- public function build($request) {
- if(Director::is_cli()) {
- $da = DatabaseAdmin::create();
- return $da->handleRequest($request, $this->model);
- } else {
- $renderer = DebugView::create();
- $renderer->writeHeader();
- $renderer->writeInfo("Environment Builder", Director::absoluteBaseURL());
- echo "";
-
- $da = DatabaseAdmin::create();
- return $da->handleRequest($request, $this->model);
-
- echo "
";
- $renderer->writeFooter();
+ public function runRegisteredController(SS_HTTPRequest $request){
+ $controllerClass = null;
+
+ $baseUrlPart = $request->param('Action');
+ $reg = Config::inst()->get(__CLASS__, 'registered_controllers');
+ if(isset($reg[$baseUrlPart])){
+ $controllerClass = $reg[$baseUrlPart]['controller'];
+ }
+
+ if($controllerClass && class_exists($controllerClass)){
+ return $controllerClass::create();
+ }
+
+ $msg = 'Error: no controller registered in '.__CLASS__.' for: '.$request->param('Action');
+ if(Director::is_cli()){
+ // in CLI we cant use httpError because of a bug with stuff being in the output already, see DevAdminControllerTest
+ throw new Exception($msg);
+ }else{
+ $this->httpError(500, $msg);
}
}
+
+
+
+
+ /*
+ * Internal methods
+ */
+
+ /**
+ * @return array of url => description
+ */
+ protected static function get_links(){
+ $links = array();
+
+ $reg = Config::inst()->get(__CLASS__, 'registered_controllers');
+ foreach($reg as $registeredController){
+ foreach($registeredController['links'] as $url => $desc){
+ $links[$url] = $desc;
+ }
+ }
+ return $links;
+ }
+
+ protected function getRegisteredController($baseUrlPart){
+ $reg = Config::inst()->get(__CLASS__, 'registered_controllers');
+
+ if(isset($reg[$baseUrlPart])){
+ $controllerClass = $reg[$baseUrlPart]['controller'];
+ return $controllerClass;
+ }
+
+ return null;
+ }
+
+
+
+
+ /*
+ * Unregistered (hidden) actions
+ */
/**
* Build the default data, calling requireDefaultRecords on all
diff --git a/tests/dev/DevAdminControllerTest.php b/tests/dev/DevAdminControllerTest.php
new file mode 100644
index 000000000..c09b14684
--- /dev/null
+++ b/tests/dev/DevAdminControllerTest.php
@@ -0,0 +1,116 @@
+update('DevelopmentAdmin', 'registered_controllers', array(
+ 'x1' => array(
+ 'controller' => 'DevAdminControllerTest_Controller1',
+ 'links' => array(
+ 'x1' => 'x1 link description',
+ 'x1/y1' => 'x1/y1 link description'
+ )
+ ),
+ 'x2' => array(
+ 'controller' => 'DevAdminControllerTest_Controller2', // intentionally not a class that exists
+ 'links' => array(
+ 'x2' => 'x2 link description'
+ )
+ )
+ ));
+ }
+
+ public function tearDown(){
+ parent::tearDown();
+ Config::unnest();
+ }
+
+
+ public function testGoodRegisteredControllerOutput(){
+ // Check for the controller running from the registered url above
+ // (we use contains rather than equals because sometimes you get Warning: You probably want to define an entry in $_FILE_TO_URL_MAPPING)
+ $this->assertContains(DevAdminControllerTest_Controller1::OK_MSG, $this->getCapture('/dev/x1'));
+ $this->assertContains(DevAdminControllerTest_Controller1::OK_MSG, $this->getCapture('/dev/x1/y1'));
+ }
+
+ public function testGoodRegisteredControllerStatus(){
+ // Check response code is 200/OK
+ $this->assertEquals(false, $this->getAndCheckForError('/dev/x1'));
+ $this->assertEquals(false, $this->getAndCheckForError('/dev/x1/y1'));
+
+ // Check response code is 500/ some sort of error
+ $this->assertEquals(true, $this->getAndCheckForError('/dev/x2'));
+ }
+
+
+
+ protected function getCapture($url){
+ $this->logInWithPermission('ADMIN');
+
+ ob_start();
+ $this->get($url);
+ $r = ob_get_contents();
+ ob_end_clean();
+
+ return $r;
+ }
+
+ protected function getAndCheckForError($url){
+ $this->logInWithPermission('ADMIN');
+
+ if(Director::is_cli()){
+ // when in CLI the admin controller throws exceptions
+ ob_start();
+ try{
+ $this->get($url);
+ }catch(Exception $e){
+ ob_end_clean();
+ return true;
+ }
+
+ ob_end_clean();
+ return false;
+
+ }else{
+ // when in http the admin controller sets a response header
+ ob_start();
+ $resp = $this->get($url);
+ ob_end_clean();
+ return $resp->isError();
+ }
+ }
+
+}
+
+class DevAdminControllerTest_Controller1 extends Controller {
+
+ const OK_MSG = 'DevAdminControllerTest_Controller1 TEST OK';
+
+ private static $url_handlers = array(
+ '' => 'index',
+ 'y1' => 'y1Action'
+ );
+
+ private static $allowed_actions = array(
+ 'index',
+ 'y1Action',
+ );
+
+
+ public function index(){
+ echo self::OK_MSG;
+ }
+
+ public function y1Action(){
+ echo self::OK_MSG;
+ }
+
+}
\ No newline at end of file
From 62f4fdb651d98c808c472bc22d894011082bdf02 Mon Sep 17 00:00:00 2001
From: Kirk Mayo
Date: Wed, 13 Aug 2014 17:29:12 +1200
Subject: [PATCH 006/119] NEW: Sanitise task name in runTask
---
dev/TaskRunner.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev/TaskRunner.php b/dev/TaskRunner.php
index 0d5df55c4..99435838c 100644
--- a/dev/TaskRunner.php
+++ b/dev/TaskRunner.php
@@ -86,7 +86,7 @@ class TaskRunner extends Controller {
}
}
- $message(sprintf('The build task "%s" could not be found', $name));
+ $message(sprintf('The build task "%s" could not be found', Convert::raw2xml($name)));
}
/**
From 4d7c6a206a47f90c42cf3432a031f60153bbe8f2 Mon Sep 17 00:00:00 2001
From: Daniel Hensby
Date: Wed, 13 Aug 2014 17:28:36 +0100
Subject: [PATCH 007/119] Fixing header case
---
control/HTTPRequest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php
index 0337c2a5c..493f43a52 100644
--- a/control/HTTPRequest.php
+++ b/control/HTTPRequest.php
@@ -395,7 +395,7 @@ class SS_HTTPRequest implements ArrayAccess {
$response = new SS_HTTPResponse($fileData);
$response->addHeader("Content-Type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
// Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
- $response->addHeader("Content-disposition", "attachment; filename=" . addslashes($fileName));
+ $response->addHeader("Content-Disposition", "attachment; filename=" . addslashes($fileName));
$response->addHeader("Content-Length", strlen($fileData));
return $response;
From 7c2eee116f49c958f6c3a8e6851db9fc2c1b0f1d Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Thu, 14 Aug 2014 12:24:56 +1200
Subject: [PATCH 008/119] BUG Fix 'undefined index 0'
---
dev/TestRunner.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev/TestRunner.php b/dev/TestRunner.php
index 7b2b23aea..4c8d9e1c1 100755
--- a/dev/TestRunner.php
+++ b/dev/TestRunner.php
@@ -314,7 +314,7 @@ class TestRunner extends Controller {
if (count($classList) > 1) {
self::$default_reporter->writeInfo("All Tests", "Running test cases: ",implode(", ", $classList));
} elseif (count($classList) == 1) {
- self::$default_reporter->writeInfo($classList[0], '');
+ self::$default_reporter->writeInfo(reset($classList), '');
} else {
// border case: no tests are available.
self::$default_reporter->writeInfo('', '');
From 5f1552b3655ec03ada86be72cb4b97eb0303e853 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Thu, 14 Aug 2014 13:50:23 +1200
Subject: [PATCH 009/119] BUG Custom label set in summary_fields config gets
overridden
---
model/DataObject.php | 10 +++++++---
tests/model/DataObjectTest.php | 20 +++++++++++++++++++-
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/model/DataObject.php b/model/DataObject.php
index 0950fef4f..3f10f4a63 100644
--- a/model/DataObject.php
+++ b/model/DataObject.php
@@ -3333,7 +3333,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*
* @return array
*/
- public function summaryFields(){
+ public function summaryFields() {
$fields = $this->stat('summary_fields');
// if fields were passed in numeric array,
@@ -3357,9 +3357,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Localize fields (if possible)
foreach($this->fieldLabels(false) as $name => $label) {
- if(isset($fields[$name])) $fields[$name] = $label;
+ // only attempt to localize if the label definition is the same as the field name.
+ // this will preserve any custom labels set in the summary_fields configuration
+ if(isset($fields[$name]) && $name === $fields[$name]) {
+ $fields[$name] = $label;
+ }
}
-
+
return $fields;
}
diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php
index bbd76436b..6f235ea75 100644
--- a/tests/model/DataObjectTest.php
+++ b/tests/model/DataObjectTest.php
@@ -682,7 +682,24 @@ class DataObjectTest extends SapphireTest {
$fields = $testObj->searchableFields();
$this->assertEmpty($fields);
}
-
+
+ public function testSummaryFieldsCustomLabels() {
+ $team = $this->objFromFixture('DataObjectTest_Team', 'team1');
+ $summaryFields = $team->summaryFields();
+
+ $this->assertEquals(
+ 'Custom Title',
+ $summaryFields['Title'],
+ 'Custom title is preserved'
+ );
+
+ $this->assertEquals(
+ 'Captain\'s shirt number',
+ $summaryFields['Captain.ShirtNumber'],
+ 'Custom title on relation is preserved'
+ );
+ }
+
public function testDataObjectUpdate() {
/* update() calls can use the dot syntax to reference has_one relations and other methods that return
* objects */
@@ -1263,6 +1280,7 @@ class DataObjectTest_Team extends DataObject implements TestOnly {
);
private static $summary_fields = array(
+ 'Title' => 'Custom Title',
'Title.UpperCase' => 'Title',
'Captain.ShirtNumber' => 'Captain\'s shirt number',
'Captain.FavouriteTeam.Title' => 'Captain\'s favourite team'
From 79c727661144d556971c05cee81152d7a13bd59e Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Fri, 15 Aug 2014 15:17:39 +1200
Subject: [PATCH 010/119] BUG Reapply fix for cms crashing due to History.js
blindly appending slashes to end of url
---
admin/javascript/LeftAndMain.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js
index c772076f6..2f6b529d3 100644
--- a/admin/javascript/LeftAndMain.js
+++ b/admin/javascript/LeftAndMain.js
@@ -374,7 +374,8 @@ jQuery.noConflict();
// Artificial HTTP referer, IE doesn't submit them via ajax.
// Also rewrites anchors to their page counterparts, which is important
// as automatic browser ajax response redirects seem to discard the hash/fragment.
- formData.push({name: 'BackURL', value:History.getPageUrl()});
+ // TODO Replaces trailing slashes added by History after locale (e.g. admin/?locale=en/)
+ formData.push({name: 'BackURL', value:History.getPageUrl().replace(/\/$/, '')});
// Save tab selections so we can restore them later
this.saveTabState();
From b9ae40177239fa64dd7ff0610bc6463c2940995c Mon Sep 17 00:00:00 2001
From: Simon Welsh
Date: Sat, 16 Aug 2014 22:08:52 +1000
Subject: [PATCH 011/119] Don't block rewriting .php URLs
---
dev/install/install.php5 | 1 -
1 file changed, 1 deletion(-)
diff --git a/dev/install/install.php5 b/dev/install/install.php5
index 5e22ff060..7602f1a4e 100755
--- a/dev/install/install.php5
+++ b/dev/install/install.php5
@@ -1528,7 +1528,6 @@ ErrorDocument 500 /assets/error-500.html
RewriteCond %{REQUEST_URI} ^(.*)$
RewriteCond %{REQUEST_FILENAME} !-f
- RewriteCond %{REQUEST_URI} !\.php$
RewriteRule .* $modulePath/main.php?url=%1&%{QUERY_STRING} [L]
TEXT;
From 8cf99b9becef0a4c4df29bad680281ea11fd96f0 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Mon, 18 Aug 2014 10:43:39 +1200
Subject: [PATCH 012/119] Fixing inconsistent use of RewriteRule in docs and
install.php5
It currently doesn't match the .htaccess that comes with a checkout of
silverstripe-installer.
---
dev/install/install.php5 | 2 +-
docs/en/reference/execution-pipeline.md | 8 +++++---
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/dev/install/install.php5 b/dev/install/install.php5
index 7602f1a4e..0d34d2d2b 100755
--- a/dev/install/install.php5
+++ b/dev/install/install.php5
@@ -1528,7 +1528,7 @@ ErrorDocument 500 /assets/error-500.html
RewriteCond %{REQUEST_URI} ^(.*)$
RewriteCond %{REQUEST_FILENAME} !-f
- RewriteRule .* $modulePath/main.php?url=%1&%{QUERY_STRING} [L]
+ RewriteRule .* $modulePath/main.php?url=%1 [QSA]
TEXT;
diff --git a/docs/en/reference/execution-pipeline.md b/docs/en/reference/execution-pipeline.md
index 00184f18d..7359a8a3e 100644
--- a/docs/en/reference/execution-pipeline.md
+++ b/docs/en/reference/execution-pipeline.md
@@ -9,7 +9,7 @@ This page documents all the steps from an URL request to the delivered page.
Silverstripe uses **[mod_rewrite](http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html)** to deal with page requests.
So instead of having your normal everyday `index.php` file which tells all, you need to look elsewhere.
-The basic .htaccess file after installing SilverStripe look like this:
+The basic .htaccess file after installing SilverStripe looks like this:
### SILVERSTRIPE START ###
@@ -23,11 +23,13 @@ The basic .htaccess file after installing SilverStripe look like this:
RewriteEngine On
- RewriteCond %{REQUEST_URI} !(\.gif$)|(\.jpg$)|(\.png$)|(\.css$)|(\.js$)
+ RewriteRule ^vendor(/|$) - [F,L,NC]
+ RewriteRule silverstripe-cache(/|$) - [F,L,NC]
+ RewriteRule composer\.(json|lock) - [F,L,NC]
RewriteCond %{REQUEST_URI} ^(.*)$
RewriteCond %{REQUEST_FILENAME} !-f
- RewriteRule .* framework/main.php?url=%1&%{QUERY_STRING} [L]
+ RewriteRule .* framework/main.php?url=%1 [QSA]
### SILVERSTRIPE END ###
From 5704ae2113b52376d97145ad14d4762b57928553 Mon Sep 17 00:00:00 2001
From: Kirk Mayo
Date: Wed, 13 Aug 2014 17:29:12 +1200
Subject: [PATCH 013/119] NEW: Sanitise task name in runTask
---
dev/TaskRunner.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev/TaskRunner.php b/dev/TaskRunner.php
index 0d5df55c4..99435838c 100644
--- a/dev/TaskRunner.php
+++ b/dev/TaskRunner.php
@@ -86,7 +86,7 @@ class TaskRunner extends Controller {
}
}
- $message(sprintf('The build task "%s" could not be found', $name));
+ $message(sprintf('The build task "%s" could not be found', Convert::raw2xml($name)));
}
/**
From 7e70b8de52e1fd0171d62809fd936e143c420153 Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Fri, 15 Aug 2014 15:17:39 +1200
Subject: [PATCH 014/119] BUG Reapply fix for cms crashing due to History.js
blindly appending slashes to end of url
---
admin/javascript/LeftAndMain.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js
index c772076f6..2f6b529d3 100644
--- a/admin/javascript/LeftAndMain.js
+++ b/admin/javascript/LeftAndMain.js
@@ -374,7 +374,8 @@ jQuery.noConflict();
// Artificial HTTP referer, IE doesn't submit them via ajax.
// Also rewrites anchors to their page counterparts, which is important
// as automatic browser ajax response redirects seem to discard the hash/fragment.
- formData.push({name: 'BackURL', value:History.getPageUrl()});
+ // TODO Replaces trailing slashes added by History after locale (e.g. admin/?locale=en/)
+ formData.push({name: 'BackURL', value:History.getPageUrl().replace(/\/$/, '')});
// Save tab selections so we can restore them later
this.saveTabState();
From f49416858cee60ccbb188c9e9e3a33eac7ce64e4 Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Mon, 18 Aug 2014 17:42:31 +1200
Subject: [PATCH 015/119] Added 3.1.6-rc3 changelog
---
docs/en/changelogs/rc/3.1.6-rc3.md | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 docs/en/changelogs/rc/3.1.6-rc3.md
diff --git a/docs/en/changelogs/rc/3.1.6-rc3.md b/docs/en/changelogs/rc/3.1.6-rc3.md
new file mode 100644
index 000000000..10b10d0ab
--- /dev/null
+++ b/docs/en/changelogs/rc/3.1.6-rc3.md
@@ -0,0 +1,16 @@
+# 3.1.6-rc3
+
+### Features and Enhancements
+
+ * 2014-08-13 [5704ae2](https://github.com/silverstripe/sapphire/commit/5704ae2) Sanitise task name in runTask (Kirk Mayo)
+
+### Bugfixes
+
+ * 2014-08-15 [7e70b8d](https://github.com/silverstripe/sapphire/commit/7e70b8d) Reapply fix for cms crashing due to History.js blindly appending slashes to end of url (Damian Mooyman)
+ * 2014-05-15 [7277dc1](https://github.com/silverstripe/silverstripe-cms/commit/7277dc1) Fix sorting on main ReportAdmin grid ref: CWPBUG-133 (Damian Mooyman)
+
+## Downloads
+
+ * [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.6-rc3)
+ * [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.6-rc3)
+ * [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.6-rc3)
From b27cd73cb97c5a2bfdc7100e5862b48f7ec47ba3 Mon Sep 17 00:00:00 2001
From: Loz Calver
Date: Mon, 18 Aug 2014 09:12:26 +0100
Subject: [PATCH 016/119] Add HTTP status code for $databaseConfig error (fixes
#3392)
---
main.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/main.php b/main.php
index 9f10da239..e294530cb 100644
--- a/main.php
+++ b/main.php
@@ -158,6 +158,7 @@ global $databaseConfig;
// Redirect to the installer if no database is selected
if(!isset($databaseConfig) || !isset($databaseConfig['database']) || !$databaseConfig['database']) {
if(!file_exists(BASE_PATH . '/install.php')) {
+ header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error");
die('SilverStripe Framework requires a $databaseConfig defined.');
}
$s = (isset($_SERVER['SSL']) || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) ? 's' : '';
From 9476b22409706666bd6f24db41cc249bca3ab14e Mon Sep 17 00:00:00 2001
From: Tobias Nyholm
Date: Fri, 8 Aug 2014 07:44:41 +0200
Subject: [PATCH 017/119] Added HHVM to .travis.yml
---
.travis.yml | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/.travis.yml b/.travis.yml
index f67dc8a59..462fb9ff2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,10 @@ language: php
php:
- 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - hhvm-nightly
env:
global:
@@ -13,6 +17,8 @@ env:
- DB=MYSQL CORE_RELEASE=3.1
matrix:
+ allow_failures:
+ - php: hhvm-nightly
include:
- php: 5.3
env: DB=PGSQL CORE_RELEASE=3.1
@@ -26,6 +32,7 @@ matrix:
env: DB=MYSQL CORE_RELEASE=3.1
- php: 5.4
env: DB=MYSQL CORE_RELEASE=3.1 BEHAT_TEST=1
+
before_script:
- composer self-update
- phpenv rehash
From 47cc157d976a0bf1fae62ef491cad9fe3e1e2c99 Mon Sep 17 00:00:00 2001
From: Jeremy Shipman
Date: Mon, 28 Jul 2014 11:57:32 +1200
Subject: [PATCH 018/119] FIX: Keep ImagickBackend API consistent with
Image_Backend interface and fix color formatting.
---
filesystem/ImagickBackend.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/filesystem/ImagickBackend.php b/filesystem/ImagickBackend.php
index 000d366ec..c5f356452 100644
--- a/filesystem/ImagickBackend.php
+++ b/filesystem/ImagickBackend.php
@@ -184,7 +184,7 @@ class ImagickBackend extends Imagick implements Image_Backend {
* @param int $height
* @return Image_Backend
*/
- public function paddedResize($width, $height, $backgroundColor = "#FFFFFF00") {
+ public function paddedResize($width, $height, $backgroundColor = "FFFFFF") {
if(!$this->valid()) return;
$width = round($width);
@@ -197,7 +197,7 @@ class ImagickBackend extends Imagick implements Image_Backend {
}
$new = clone $this;
- $new->setBackgroundColor($backgroundColor);
+ $new->setBackgroundColor("#".$backgroundColor);
$destAR = $width / $height;
if ($geometry["width"] > 0 && $geometry["height"] > 0) {
From 02265dc3c4a6d2fca20773edb5ac0938574d5d68 Mon Sep 17 00:00:00 2001
From: Jeremy Shipman
Date: Mon, 28 Jul 2014 12:19:34 +1200
Subject: [PATCH 019/119] FIX: Correctly paddedResize images in IMagickBackend.
FIX: Compression quality setting now takes effect.
fixes #3333
---
filesystem/ImagickBackend.php | 47 +++++------------------------------
1 file changed, 6 insertions(+), 41 deletions(-)
diff --git a/filesystem/ImagickBackend.php b/filesystem/ImagickBackend.php
index c5f356452..729042363 100644
--- a/filesystem/ImagickBackend.php
+++ b/filesystem/ImagickBackend.php
@@ -23,9 +23,8 @@ class ImagickBackend extends Imagick implements Image_Backend {
public function __construct($filename = null) {
if(is_string($filename)) {
parent::__construct($filename);
- } else {
- self::setImageCompressionQuality($this->config()->default_quality);
}
+ $this->setQuality(Config::inst()->get('ImagickBackend','default_quality'));
}
/**
@@ -185,45 +184,11 @@ class ImagickBackend extends Imagick implements Image_Backend {
* @return Image_Backend
*/
public function paddedResize($width, $height, $backgroundColor = "FFFFFF") {
- if(!$this->valid()) return;
-
- $width = round($width);
- $height = round($height);
- $geometry = $this->getImageGeometry();
-
- // Check that a resize is actually necessary.
- if ($width == $geometry["width"] && $height == $geometry["height"]) {
- return $this;
- }
-
- $new = clone $this;
- $new->setBackgroundColor("#".$backgroundColor);
-
- $destAR = $width / $height;
- if ($geometry["width"] > 0 && $geometry["height"] > 0) {
- // We can't divide by zero theres something wrong.
-
- $srcAR = $geometry["width"] / $geometry["height"];
-
- // Destination narrower than the source
- if($destAR > $srcAR) {
- $destY = 0;
- $destHeight = $height;
-
- $destWidth = round( $height * $srcAR );
- $destX = round( ($width - $destWidth) / 2 );
-
- // Destination shorter than the source
- } else {
- $destX = 0;
- $destWidth = $width;
-
- $destHeight = round( $width / $srcAR );
- $destY = round( ($height - $destHeight) / 2 );
- }
-
- $new->extentImage($width, $height, $destX, $destY);
- }
+ $new = $this->resizeRatio($width, $height);
+ $new->setImageBackgroundColor("#".$backgroundColor);
+ $w = $new->getImageWidth();
+ $h = $new->getImageHeight();
+ $new->extentImage($width,$height,($w-$width)/2,($h-$height)/2);
return $new;
}
From bf3ad5667422e7e33a38dbfe85ed68e5c1170fbb Mon Sep 17 00:00:00 2001
From: Jeremy Shipman
Date: Mon, 28 Jul 2014 11:01:05 +1200
Subject: [PATCH 020/119] FIX: Image_Backend -> croppedResize function doesn't
include a backgroundColor, therefore this shouldn't be assumed in
ImageMagick->croppedResize
---
filesystem/ImagickBackend.php | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/filesystem/ImagickBackend.php b/filesystem/ImagickBackend.php
index 000d366ec..066daa404 100644
--- a/filesystem/ImagickBackend.php
+++ b/filesystem/ImagickBackend.php
@@ -247,12 +247,8 @@ class ImagickBackend extends Imagick implements Image_Backend {
return $this;
}
- if(!$backgroundColor){
- $backgroundColor = new ImagickPixel('transparent');
- }
-
$new = clone $this;
- $new->setBackgroundColor($backgroundColor);
+ $new->setBackgroundColor(new ImagickPixel('transparent'));
if(($geo['width']/$width) < ($geo['height']/$height)){
$new->cropImage($geo['width'], floor($height*$geo['width']/$width),
From 4178f7beb07691bc608ed6d9393e7ec05d29cd90 Mon Sep 17 00:00:00 2001
From: Devlin
Date: Thu, 17 Jul 2014 11:54:23 +0200
Subject: [PATCH 021/119] test all generate methods and formatted image
deletetions
---
model/Image.php | 2 +-
tests/model/ImageTest.php | 62 +++++++++++++++++++++++++++++++++++++++
2 files changed, 63 insertions(+), 1 deletion(-)
diff --git a/model/Image.php b/model/Image.php
index 868f60cab..f08bc3a16 100644
--- a/model/Image.php
+++ b/model/Image.php
@@ -582,7 +582,7 @@ class Image extends File {
}
// All generate functions may appear any number of times in the image cache name.
$generateFuncs = implode('|', $generateFuncs);
- $pattern = "/^(({$generateFuncs})\d+\-)+" . preg_quote($this->Name) . "$/i";
+ $pattern = "/^(({$generateFuncs}).*\-)+" . preg_quote($this->Name) . "$/i";
foreach($cachedFiles as $cfile) {
if(preg_match($pattern, $cfile)) {
diff --git a/tests/model/ImageTest.php b/tests/model/ImageTest.php
index ca916b722..70788dee1 100644
--- a/tests/model/ImageTest.php
+++ b/tests/model/ImageTest.php
@@ -268,4 +268,66 @@ class ImageTest extends SapphireTest {
$secondImagePath = $secondImage->getRelativePath();
$this->assertEquals($secondImagePath, $secondImage->Filename);
}
+
+ /**
+ * Test all generate methods
+ */
+ public function testGenerateMethods() {
+ $image = $this->objFromFixture('Image', 'imageWithoutTitle');
+ $generateMethods = $this->getGenerateMethods();
+
+ // test each generate method
+ foreach ($generateMethods as $method) {
+ $generatedImage = $image->$method(333, 333, 'FFFFFF');
+ $this->assertFileExists(
+ $generatedImage->getFullPath(),
+ 'Formatted ' . $method . ' image exists'
+ );
+ }
+ }
+
+ /**
+ * Test deleteFormattedImages() against all generate methods
+ */
+ public function testDeleteFormattedImages() {
+ $image = $this->objFromFixture('Image', 'imageWithoutTitle');
+ $generateMethods = $this->getGenerateMethods();
+
+ // get paths for each generate method
+ $paths = array();
+ foreach ($generateMethods as $method) {
+ $generatedImage = $image->$method(333, 333, 'FFFFFF');
+ $paths[$method] = $generatedImage->getFullPath();
+ }
+
+ // delete formatted images
+ $image->deleteFormattedImages();
+
+ // test that all formatted images are deleted
+ foreach ($paths as $method => $path) {
+ $this->assertFalse(
+ file_exists($path),
+ 'Formatted ' . $method . ' image does not exist'
+ );
+ }
+ }
+
+ /**
+ * @param bool $custom include methods added dynamically at runtime
+ * @return array
+ */
+ protected function getGenerateMethods($custom = true) {
+ $generateMethods = array();
+ $methodNames = Image::create()->allMethodNames($custom);
+
+ foreach ($methodNames as $methodName) {
+ if (substr($methodName, 0, 8) == 'generate' && $methodName != 'generateformattedimage') {
+ $format = substr($methodName, 8);
+ $generateMethods[] = $format;
+ }
+ }
+
+ return $generateMethods;
+ }
+
}
From 61c6dee057d72182a2ff917b069ba993c6e4d32b Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Wed, 20 Aug 2014 14:50:42 +1200
Subject: [PATCH 022/119] BUG Fixing plural_name messing up singular words
ending in "e" (#3251)
This would ideally be fixed with the ability to use an external library
like gettext, but that's an API change. This for now fixes the issue
where a singular like "Page" returns "Pags" for the plural name.
---
model/DataObject.php | 4 +---
tests/model/DataObjectTest.php | 25 ++++++++++++++++++++++---
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/model/DataObject.php b/model/DataObject.php
index 3f10f4a63..4c0a5f076 100644
--- a/model/DataObject.php
+++ b/model/DataObject.php
@@ -713,9 +713,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $name;
} else {
$name = $this->singular_name();
- if(substr($name,-1) == 'e') $name = substr($name,0,-1);
- else if(substr($name,-1) == 'y') $name = substr($name,0,-1) . 'ie';
-
+ if(substr($name,-1) == 'y') $name = substr($name,0,-1) . 'ie';
return ucfirst($name . 's');
}
}
diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php
index 6f235ea75..ae6761038 100644
--- a/tests/model/DataObjectTest.php
+++ b/tests/model/DataObjectTest.php
@@ -949,21 +949,40 @@ class DataObjectTest extends SapphireTest {
* Tests that singular_name() generates sensible defaults.
*/
public function testSingularName() {
- $assertions = array (
+ $assertions = array(
'DataObjectTest_Player' => 'Data Object Test Player',
'DataObjectTest_Team' => 'Data Object Test Team',
'DataObjectTest_Fixture' => 'Data Object Test Fixture'
);
foreach($assertions as $class => $expectedSingularName) {
- $this->assertEquals (
+ $this->assertEquals(
$expectedSingularName,
singleton($class)->singular_name(),
"Assert that the singular_name for '$class' is correct."
);
}
}
-
+
+ /**
+ * Tests that plural_name() generates sensible defaults.
+ */
+ public function testPluralName() {
+ $assertions = array(
+ 'DataObjectTest_Player' => 'Data Object Test Players',
+ 'DataObjectTest_Team' => 'Data Object Test Teams',
+ 'DataObjectTest_Fixture' => 'Data Object Test Fixtures'
+ );
+
+ foreach($assertions as $class => $expectedPluralName) {
+ $this->assertEquals(
+ $expectedPluralName,
+ singleton($class)->plural_name(),
+ "Assert that the plural_name for '$class' is correct."
+ );
+ }
+ }
+
public function testHasDatabaseField() {
$team = singleton('DataObjectTest_Team');
$subteam = singleton('DataObjectTest_SubTeam');
From 8e060d159fe410c354f67d30fd866ac564b10d5e Mon Sep 17 00:00:00 2001
From: Loz Calver
Date: Tue, 19 Aug 2014 15:36:44 +0100
Subject: [PATCH 023/119] Fix: Make CSSContentParser's use of 'Tidy' consistent
---
dev/CSSContentParser.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dev/CSSContentParser.php b/dev/CSSContentParser.php
index af29ce0a0..185785919 100644
--- a/dev/CSSContentParser.php
+++ b/dev/CSSContentParser.php
@@ -30,7 +30,7 @@ class CSSContentParser extends Object {
array(
'output-xhtml' => true,
'numeric-entities' => true,
- 'wrap' => 99999, // We need this to be consistent for functional test string comparisons
+ 'wrap' => 0, // We need this to be consistent for functional test string comparisons
),
'utf8'
);
@@ -41,7 +41,7 @@ class CSSContentParser extends Object {
} elseif(@shell_exec('which tidy')) {
// using tiny through cli
$CLI_content = escapeshellarg($content);
- $tidy = `echo $CLI_content | tidy -n -q -utf8 -asxhtml 2> /dev/null`;
+ $tidy = `echo $CLI_content | tidy -n -q -utf8 -asxhtml -w 0 2> /dev/null`;
$tidy = str_replace('xmlns="http://www.w3.org/1999/xhtml"','',$tidy);
$tidy = str_replace(' ','',$tidy);
} else {
From 8c12af6acbcdd6df102417e297513d4b98286850 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=98=83=20Stephen=20Shkardoon=20=E2=98=83?=
Date: Wed, 20 Aug 2014 21:52:14 +1200
Subject: [PATCH 024/119] Update urlvariabletools.md
Remove strange reference to flush values in showtemplate line.
---
docs/en/reference/urlvariabletools.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/en/reference/urlvariabletools.md b/docs/en/reference/urlvariabletools.md
index 4352a3689..66c028665 100644
--- a/docs/en/reference/urlvariabletools.md
+++ b/docs/en/reference/urlvariabletools.md
@@ -18,7 +18,7 @@ Append the option and corresponding value to your URL in your browser's address
| URL Variable | | Values | | Description |
| ------------ | | ------ | | ----------- |
| flush=1 | | 1 | | Clears out all caches. Used mainly during development, e.g. when adding new classes or templates. Requires "dev" mode or ADMIN login |
- | showtemplate | | 1 | | Show the compiled version of all the templates used, including line numbers. Good when you have a syntax error in a template. Cannot be used on a Live site without **isDev**. **flush** can be used with the following values: |
+ | showtemplate | | 1 | | Show the compiled version of all the templates used, including line numbers. Good when you have a syntax error in a template. Cannot be used on a Live site without **isDev**. |
## General Testing
From fcdcc3889aebdcd11d1dca3c87891babf43ff311 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Thu, 21 Aug 2014 13:56:05 +1200
Subject: [PATCH 025/119] CSSContentParser: Fix mispelled "tidy" and correct
case of "tidy" class
---
dev/CSSContentParser.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/dev/CSSContentParser.php b/dev/CSSContentParser.php
index af29ce0a0..255940fbf 100644
--- a/dev/CSSContentParser.php
+++ b/dev/CSSContentParser.php
@@ -8,7 +8,7 @@
* It was built to facilitate testing using PHPUnit and contains a number of assert methods that will throw PHPUnit
* assertion exception when applicable.
*
- * Tries to use the PHP Tidy extension (http://php.net/tidy),
+ * Tries to use the PHP tidy extension (http://php.net/tidy),
* and falls back to the "tidy" CLI tool. If none of those exists,
* the string is parsed directly without sanitization.
*
@@ -23,8 +23,8 @@ class CSSContentParser extends Object {
public function __construct($content) {
if(extension_loaded('tidy')) {
- // using the tiny php extension
- $tidy = new Tidy();
+ // using the tidy php extension
+ $tidy = new tidy();
$tidy->parseString(
$content,
array(
From 0b5599e21bdc70e8e7ea965ef01eb6144d527439 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Thu, 21 Aug 2014 17:30:03 +1200
Subject: [PATCH 026/119] Remove suppression operator on session_start()
This was originally added to stop PHP on Windows complaining that it
couldn't garbage collect old sessions, but that was for an old version
of PHP years ago and doesn't seem to be an issue any longer. We
really shouldn't be suppressing this. If session_start() threw a
warning that it couldn't write to the filesystem, then we wouldn't
know about it at all.
---
control/Session.php | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/control/Session.php b/control/Session.php
index 71886726e..37fb3e65b 100644
--- a/control/Session.php
+++ b/control/Session.php
@@ -376,10 +376,8 @@ class Session {
// seperate (less secure) session for non-HTTPS requests
if($secure) session_name('SECSESSID');
- // @ is to supress win32 warnings/notices when session wasn't cleaned up properly
- // There's nothing we can do about this, because it's an operating system function!
if($sid) session_id($sid);
- @session_start();
+ session_start();
$this->data = isset($_SESSION) ? $_SESSION : array();
}
From d89ed7c4a75a8989e60c1400bb337a86165f1936 Mon Sep 17 00:00:00 2001
From: Christopher Darling
Date: Thu, 21 Aug 2014 14:31:55 +0100
Subject: [PATCH 027/119] DOCS: correct notes on TEMP_FOLDER
getTempParentFolder() in framewor/core/TempPath.php checks for silverstripe-cache directory in webroot first, failing that it falls back on the sys_get_temp_dir() folder.
also, getTempFolder() is no longer in framework/core/Core.php since #b075fa29c59f970bea31bbe8be1bd6560a8778b6, it is now located in framework/core/TempPath.php
---
docs/en/topics/environment-management.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/en/topics/environment-management.md b/docs/en/topics/environment-management.md
index fb2fe7c21..ea5267095 100644
--- a/docs/en/topics/environment-management.md
+++ b/docs/en/topics/environment-management.md
@@ -105,7 +105,7 @@ 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 *sys_get_temp_dir()*, and falls back to *silverstripe-cache* in the webroot. See *getTempFolder()* in *framework/core/Core.php* |
+| `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)|
From 2b316e79e5f957bfe06385f762c237acaf75b0e7 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Sat, 16 Aug 2014 12:51:17 +1200
Subject: [PATCH 028/119] NEW Provide a consistent way of triggering flush
Provides an interface for classes to implement their own flush()
functionality. This function gets called early in a request on
all implementations of Flushable when flush=1|all is requested in the
URL.
This fix came out of an issue where Requirements combined files were not
being cleaned up after dev/build?flush=1, due to the fact that flush
would only occur when you called it while on a page that used those
combined files, but not in any other contexts. This will now call flush
on any implementors of Flushable regardless of the context of where
flush was called.
---
_config/requestprocessors.yml | 1 +
api/RestfulService.php | 17 +++++++-
control/FlushRequestFilter.php | 24 +++++++++++
control/RequestProcessor.php | 2 +-
core/Flushable.php | 20 +++++++++
dev/TestRunner.php | 27 ++++++------
docs/en/howto/phpunit-configuration.md | 20 +++++++--
docs/en/reference/execution-pipeline.md | 9 ++++
docs/en/reference/flushable.md | 56 +++++++++++++++++++++++++
docs/en/topics/caching.md | 9 +++-
i18n/i18n.php | 31 ++++++++++----
model/Image.php | 21 ++++++++--
tests/bootstrap.php | 7 ----
view/Requirements.php | 34 +++++++++++++--
view/SSViewer.php | 27 +++++-------
15 files changed, 244 insertions(+), 61 deletions(-)
create mode 100644 control/FlushRequestFilter.php
create mode 100644 core/Flushable.php
create mode 100644 docs/en/reference/flushable.md
diff --git a/_config/requestprocessors.yml b/_config/requestprocessors.yml
index 1e9310bd3..17ee24025 100644
--- a/_config/requestprocessors.yml
+++ b/_config/requestprocessors.yml
@@ -5,4 +5,5 @@ Injector:
RequestProcessor:
properties:
filters:
+ - '%$FlushRequestFilter'
- '%$VersionedRequestFilter'
diff --git a/api/RestfulService.php b/api/RestfulService.php
index 5f8d7ae29..9d1116d42 100644
--- a/api/RestfulService.php
+++ b/api/RestfulService.php
@@ -9,7 +9,7 @@
* @package framework
* @subpackage integration
*/
-class RestfulService extends ViewableData {
+class RestfulService extends ViewableData implements Flushable {
protected $baseURL;
protected $queryString;
@@ -33,6 +33,19 @@ class RestfulService extends ViewableData {
*/
private static $default_curl_options = array();
+ /**
+ * @config
+ * @var bool Flushes caches if set to true. This is set by {@link flush()}
+ */
+ private static $flush = false;
+
+ /**
+ * Triggered early in the request when someone requests a flush.
+ */
+ public static function flush() {
+ self::$flush = true;
+ }
+
/**
* set a curl option that will be applied to all requests as default
* {@see http://php.net/manual/en/function.curl-setopt.php#refsect1-function.curl-setopt-parameters}
@@ -171,7 +184,7 @@ class RestfulService extends ViewableData {
// Check for unexpired cached feed (unless flush is set)
//assume any cache_expire that is 0 or less means that we dont want to
// cache
- if($this->cache_expire > 0 && !isset($_GET['flush'])
+ if($this->cache_expire > 0 && self::$flush
&& @file_exists($cache_path)
&& @filemtime($cache_path) + $this->cache_expire > time()) {
diff --git a/control/FlushRequestFilter.php b/control/FlushRequestFilter.php
new file mode 100644
index 000000000..ab6be8dd4
--- /dev/null
+++ b/control/FlushRequestFilter.php
@@ -0,0 +1,24 @@
+getVar('flush')) {
+ foreach(ClassInfo::implementorsOf('Flushable') as $class) {
+ $class::flush();
+ }
+ }
+
+ return true;
+ }
+
+ public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model) {
+ return true;
+ }
+
+}
diff --git a/control/RequestProcessor.php b/control/RequestProcessor.php
index e391be0a5..566ad8017 100644
--- a/control/RequestProcessor.php
+++ b/control/RequestProcessor.php
@@ -45,4 +45,4 @@ class RequestProcessor implements RequestFilter {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/core/Flushable.php b/core/Flushable.php
new file mode 100644
index 000000000..4b1781bb9
--- /dev/null
+++ b/core/Flushable.php
@@ -0,0 +1,20 @@
+pushManifest($classManifest, false);
SapphireTest::set_test_class_manifest($classManifest);
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
- BASE_PATH, project(), true, isset($_GET['flush'])
+ BASE_PATH, project(), true, $flush
));
Config::inst()->pushConfigStaticManifest(new SS_ConfigStaticManifest(
- BASE_PATH, true, isset($_GET['flush'])
+ BASE_PATH, true, $flush
));
-
+
// Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
// (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
DataObject::clear_classname_spec_cache();
@@ -109,14 +114,6 @@ class TestRunner extends Controller {
if(!PhpUnitWrapper::has_php_unit()) {
die("Please install PHPUnit using pear");
}
-
- if(!isset($_GET['flush']) || !$_GET['flush']) {
- Debug::message(
- "WARNING: Manifest not flushed. " .
- "Add flush=1 as an argument to discover new classes or files.\n",
- false
- );
- }
}
public function Link() {
diff --git a/docs/en/howto/phpunit-configuration.md b/docs/en/howto/phpunit-configuration.md
index 4ac184feb..92db5bfbe 100644
--- a/docs/en/howto/phpunit-configuration.md
+++ b/docs/en/howto/phpunit-configuration.md
@@ -76,17 +76,31 @@ Example `mysite/_config.php`:
if($db == 'sqlite3') $databaseConfig['type'] = 'SQLite3Database';
}
}
-
+
You can either use the database on a single invocation:
phpunit framework/tests "" db=sqlite3
-
+
or through a `` flag in your `phpunit.xml` (see [Appenix C: "Setting PHP INI settings"](http://www.phpunit.de/manual/current/en/appendixes.configuration.html)):
-
+
+
+
+
+Note that on every test run, the manifest is flushed to avoid any bugs where a test doesn't clean up after
+itself properly. You can override that behaviour by passing `flush=0` to the test command:
+
+ phpunit framework/tests flush=0
+
+Alternatively, you can set the var in your `phpunit.xml` file:
+
+
+
+
+
diff --git a/docs/en/reference/execution-pipeline.md b/docs/en/reference/execution-pipeline.md
index 00184f18d..993250a87 100644
--- a/docs/en/reference/execution-pipeline.md
+++ b/docs/en/reference/execution-pipeline.md
@@ -91,3 +91,12 @@ You can access the following controller-method with /team/signup
## SSViewer template rendering
See [templates](/reference/templates) for information on the SSViewer template system.
+
+## Flush requests
+
+If `?flush=1` is requested in the URL, e.g. http://mysite.com?flush=1, this will trigger a call to `flush()` on
+any classes that implement the `Flushable` interface.
+
+See [reference documentation on Flushable](/reference/flushable) for more details.
+
+
diff --git a/docs/en/reference/flushable.md b/docs/en/reference/flushable.md
new file mode 100644
index 000000000..b24634c57
--- /dev/null
+++ b/docs/en/reference/flushable.md
@@ -0,0 +1,56 @@
+# Flushable
+
+## Introduction
+
+Allows a class to define it's own flush functionality, which is triggered when `flush=1` is requested in the URL.
+
+`[api:FlushRequestFilter]` is run before a request is made, calling `flush()` statically on all
+implementors of `[api:Flushable]`.
+
+## Usage
+
+To use this API, you need to make your class implement `[api:Flushable]`, and define a `flush()` static function,
+this defines the actions that need to be executed on a flush request.
+
+### Using with SS_Cache
+
+This example uses `[api:SS_Cache]` in some custom code, and the same cache is cleaned on flush:
+
+ :::php
+ clean(Zend_Cache::CLEANING_MODE_ALL);
+ }
+
+ public function MyCachedContent() {
+ $cache = SS_Cache::factory('mycache')
+ $something = $cache->get('mykey');
+ if(!$something) {
+ $something = 'value to be cached';
+ $cache->save($something);
+ }
+ return $something;
+ }
+
+ }
+
+### Using with filesystem
+
+Another example, some temporary files are created in a directory in assets, and are deleted on flush. This would be
+useful in an example like `GD` or `Imagick` generating resampled images, but we want to delete any cached images on
+flush so they are re-created on demand.
+
+ :::php
+ TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache'
)
));
- SS_Cache::pick_backend('two_level', 'any', 10);
\ No newline at end of file
+ SS_Cache::pick_backend('two_level', 'any', 10);
diff --git a/i18n/i18n.php b/i18n/i18n.php
index 4b81fe1c9..4acc5eb1a 100644
--- a/i18n/i18n.php
+++ b/i18n/i18n.php
@@ -61,7 +61,7 @@ require_once 'i18nSSLegacyAdapter.php';
* @package framework
* @subpackage misc
*/
-class i18n extends Object implements TemplateGlobalProvider {
+class i18n extends Object implements TemplateGlobalProvider, Flushable {
/**
* This static variable is used to store the current defined locale.
@@ -97,6 +97,21 @@ class i18n extends Object implements TemplateGlobalProvider {
*/
protected static $translators;
+ /**
+ * Triggered early in the request when someone requests a flush.
+ */
+ public static function flush() {
+ self::get_cache()->clean(Zend_Cache::CLEANING_MODE_ALL);
+ }
+
+ /**
+ * Return an instance of the cache used for i18n data.
+ * @return Zend_Cache
+ */
+ public static function get_cache() {
+ return SS_Cache::factory('i18n', 'Output', array('lifetime' => null, 'automatic_serialization' => true));
+ }
+
/**
* Use javascript i18n through the ss.i18n class (enabled by default).
* If set to TRUE, includes javascript requirements for the base library
@@ -2039,11 +2054,11 @@ class i18n extends Object implements TemplateGlobalProvider {
// which is instanciated by core with a $clean instance variable.
if(!$adapter->isAvailable($lang)) {
- i18n::include_by_locale($lang, (isset($_GET['flush'])));
+ i18n::include_by_locale($lang);
}
if(!$adapter->isAvailable($locale)) {
- i18n::include_by_locale($locale, (isset($_GET['flush'])));
+ i18n::include_by_locale($locale);
}
$translation = $adapter->translate($entity, $locale);
@@ -2107,9 +2122,7 @@ class i18n extends Object implements TemplateGlobalProvider {
*/
public static function get_translators() {
if(!Zend_Translate::getCache()) {
- Zend_Translate::setCache(
- SS_Cache::factory('i18n', 'Output', array('lifetime' => null, 'automatic_serialization' => true))
- );
+ Zend_Translate::setCache(self::get_cache());
}
if(!self::$translators) {
@@ -2122,8 +2135,8 @@ class i18n extends Object implements TemplateGlobalProvider {
))
);
- i18n::include_by_locale('en', isset($_GET['flush']));
- i18n::include_by_locale('en_US', isset($_GET['flush']));
+ i18n::include_by_locale('en');
+ i18n::include_by_locale('en_US');
}
return self::$translators;
@@ -2515,7 +2528,7 @@ class i18n extends Object implements TemplateGlobalProvider {
*/
public static function include_by_locale($locale, $clean = false) {
if($clean) {
- Zend_Translate::clearCache();
+ self::flush();
}
// Get list of module => path pairs, and then just the names
diff --git a/model/Image.php b/model/Image.php
index 868f60cab..6cdb17c6a 100644
--- a/model/Image.php
+++ b/model/Image.php
@@ -6,7 +6,7 @@
* @package framework
* @subpackage filesystem
*/
-class Image extends File {
+class Image extends File implements Flushable {
const ORIENTATION_SQUARE = 0;
const ORIENTATION_PORTRAIT = 1;
@@ -71,11 +71,24 @@ class Image extends File {
* @var bool Force all images to resample in all cases
*/
private static $force_resample = false;
-
+
+ /**
+ * @config
+ * @var bool Regenerates images if set to true. This is set by {@link flush()}
+ */
+ private static $flush = false;
+
+ /**
+ * Triggered early in the request when someone requests a flush.
+ */
+ public static function flush() {
+ self::$flush = true;
+ }
+
public static function set_backend($backend) {
self::config()->backend = $backend;
}
-
+
public static function get_backend() {
return self::config()->backend;
}
@@ -424,7 +437,7 @@ class Image extends File {
if($this->ID && $this->Filename && Director::fileExists($this->Filename)) {
$cacheFile = call_user_func_array(array($this, "cacheFilename"), $args);
- if(!file_exists(Director::baseFolder()."/".$cacheFile) || isset($_GET['flush'])) {
+ if(!file_exists(Director::baseFolder()."/".$cacheFile) || self::$flush) {
call_user_func_array(array($this, "generateFormattedImage"), $args);
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index bd02b154e..d95e29bbc 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -66,10 +66,3 @@ SapphireTest::set_is_running_test(true);
// Remove the error handler so that PHPUnit can add its own
restore_error_handler();
-if(!isset($_GET['flush']) || !$_GET['flush']) {
- Debug::message(
- "WARNING: Manifest not flushed. " .
- "Add flush=1 as an argument to discover new classes or files.\n",
- false
- );
-}
diff --git a/view/Requirements.php b/view/Requirements.php
index 7db31e0d9..8848c6087 100644
--- a/view/Requirements.php
+++ b/view/Requirements.php
@@ -7,7 +7,14 @@
* @package framework
* @subpackage view
*/
-class Requirements {
+class Requirements implements Flushable {
+
+ /**
+ * Triggered early in the request when someone requests a flush.
+ */
+ public static function flush() {
+ self::delete_all_combined_files();
+ }
/**
* Enable combining of css/javascript files.
@@ -272,6 +279,13 @@ class Requirements {
return self::backend()->delete_combined_files($combinedFileName);
}
+ /**
+ * Deletes all generated combined files in the configured combined files directory,
+ * but doesn't delete the directory itself.
+ */
+ public static function delete_all_combined_files() {
+ return self::backend()->delete_all_combined_files();
+ }
/**
* Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()}
@@ -455,7 +469,7 @@ class Requirements_Backend {
* @var boolean
*/
protected $force_js_to_bottom = false;
-
+
public function set_combined_files_enabled($enable) {
$this->combined_files_enabled = (bool) $enable;
}
@@ -1005,6 +1019,20 @@ class Requirements_Backend {
}
}
+ /**
+ * Deletes all generated combined files in the configured combined files directory,
+ * but doesn't delete the directory itself.
+ */
+ public function delete_all_combined_files() {
+ $combinedFolder = $this->getCombinedFilesFolder();
+ if(!$combinedFolder) return false;
+
+ $path = Director::baseFolder() . '/' . $combinedFolder;
+ if(file_exists($path)) {
+ Filesystem::removeFolder($path, true);
+ }
+ }
+
public function clear_combined_files() {
$this->combine_files = array();
}
@@ -1085,7 +1113,7 @@ class Requirements_Backend {
}
// Determine if we need to build the combined include
- if(file_exists($combinedFilePath) && !isset($_GET['flush'])) {
+ if(file_exists($combinedFilePath)) {
// file exists, check modification date of every contained file
$srcLastMod = 0;
foreach($fileList as $file) {
diff --git a/view/SSViewer.php b/view/SSViewer.php
index d5fc9fba1..f426b3d9c 100644
--- a/view/SSViewer.php
+++ b/view/SSViewer.php
@@ -555,7 +555,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
* Caching
*
* Compiled templates are cached via {@link SS_Cache}, usually on the filesystem.
- * If you put ?flush=all on your URL, it will force the template to be recompiled.
+ * If you put ?flush=1 on your URL, it will force the template to be recompiled.
*
* @see http://doc.silverstripe.org/themes
* @see http://doc.silverstripe.org/themes:developing
@@ -563,7 +563,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
* @package framework
* @subpackage view
*/
-class SSViewer {
+class SSViewer implements Flushable {
/**
* @config
@@ -638,6 +638,13 @@ class SSViewer {
*/
private static $global_key = '$CurrentReadingMode, $CurrentUser.ID';
+ /**
+ * Triggered early in the request when someone requests a flush.
+ */
+ public static function flush() {
+ self::flush_template_cache();
+ }
+
/**
* Create a template from a string instead of a .ss file
*
@@ -744,18 +751,6 @@ class SSViewer {
public function __construct($templateList, TemplateParser $parser = null) {
$this->setParser($parser ?: Injector::inst()->get('SSTemplateParser'));
- // flush template manifest cache if requested
- if (isset($_GET['flush']) && $_GET['flush'] == 'all') {
- if(Director::isDev() || Director::is_cli() || Permission::check('ADMIN')) {
- self::flush_template_cache();
- } else {
- if(!Security::ignore_disallowed_actions()) {
- return Security::permissionFailure(null, 'Please log in as an administrator to flush ' .
- 'the template cache.');
- }
- }
- }
-
if(!is_array($templateList) && substr((string) $templateList,-3) == '.ss') {
$this->chosenTemplates['main'] = $templateList;
} else {
@@ -927,7 +922,7 @@ class SSViewer {
if (!self::$flushed) {
$dir = dir(TEMP_FOLDER);
while (false !== ($file = $dir->read())) {
- if (strstr($file, '.cache')) { unlink(TEMP_FOLDER.'/'.$file); }
+ if (strstr($file, '.cache')) unlink(TEMP_FOLDER . '/' . $file);
}
self::$flushed = true;
}
@@ -1038,7 +1033,7 @@ class SSViewer {
. str_replace(array('\\','/',':'), '.', Director::makeRelative(realpath($template)));
$lastEdited = filemtime($template);
- if(!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited || isset($_GET['flush'])) {
+ if(!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited) {
$content = file_get_contents($template);
$content = $this->parseTemplateContent($content, $template);
From 03c7231fcd89a37d19a392a861c68a61d4602ef0 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Thu, 21 Aug 2014 18:01:26 +1200
Subject: [PATCH 029/119] Fixing HHVM builds not having tidy support built-in
to PHP (#3414)
Installs cli tidy, which is used as a fallback for CSSContentParser.
---
.travis.yml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 462fb9ff2..a65b00ffe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,6 @@ php:
- 5.4
- 5.5
- 5.6
- - hhvm-nightly
env:
global:
@@ -32,6 +31,11 @@ matrix:
env: DB=MYSQL CORE_RELEASE=3.1
- php: 5.4
env: DB=MYSQL CORE_RELEASE=3.1 BEHAT_TEST=1
+ - php: hhvm-nightly
+ env: DB=MYSQL CORE_RELEASE=3.1
+ before_install:
+ - sudo apt-get update -qq
+ - sudo apt-get install -y tidy
before_script:
- composer self-update
From 0c8568037168f95da455d21febcbce1945169064 Mon Sep 17 00:00:00 2001
From: Joel Marcey
Date: Mon, 3 Feb 2014 15:32:58 -0800
Subject: [PATCH 030/119] Support PHPUnit 3.8+ compatibility
Summary:
PHPUnit 3.8+ adds a method to its PHPUnit_Framework_TestListener called addRiskyTest(). Need to stub it out to avoid "must implement this interface method" fatals when using 3.8+
Test Plan:
Reviewers:
CC:
Task ID: #
Blame Rev:
---
dev/SapphireTestReporter.php | 12 ++++++++++++
dev/SilverStripeListener.php | 12 ++++++++++++
dev/TeamCityListener.php | 12 ++++++++++++
dev/TestListener.php | 12 ++++++++++++
4 files changed, 48 insertions(+)
diff --git a/dev/SapphireTestReporter.php b/dev/SapphireTestReporter.php
index 84da76c72..602520d0e 100644
--- a/dev/SapphireTestReporter.php
+++ b/dev/SapphireTestReporter.php
@@ -300,6 +300,18 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener {
}
}
+ /**
+ * Risky test.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param Exception $e
+ * @param float $time
+ * @since Method available since Release 3.8.0
+ */
+ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
+ // Stub out to support PHPUnit 3.8
+ }
+
/**
* Trys to get the original exception thrown by the test on failure/error
* to enable us to give a bit more detail about the failure/error
diff --git a/dev/SilverStripeListener.php b/dev/SilverStripeListener.php
index 009591f82..0d6b7ceb8 100644
--- a/dev/SilverStripeListener.php
+++ b/dev/SilverStripeListener.php
@@ -42,4 +42,16 @@ class SilverStripeListener implements PHPUnit_Framework_TestListener {
public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
}
+
+ /**
+ * Risky test.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param Exception $e
+ * @param float $time
+ * @since Method available since Release 3.8.0
+ */
+ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
+ // Stub out to support PHPUnit 3.8
+ }
}
diff --git a/dev/TeamCityListener.php b/dev/TeamCityListener.php
index 7af8a6c52..48e017594 100644
--- a/dev/TeamCityListener.php
+++ b/dev/TeamCityListener.php
@@ -58,4 +58,16 @@ class TeamCityListener implements PHPUnit_Framework_TestListener {
$message = $this->escape($e->getMessage());
echo "##teamcity[testIgnored name='{$class}.{$test->getName()}' message='$message']\n";
}
+
+ /**
+ * Risky test.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param Exception $e
+ * @param float $time
+ * @since Method available since Release 3.8.0
+ */
+ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
+ // Stub out to support PHPUnit 3.8
+ }
}
diff --git a/dev/TestListener.php b/dev/TestListener.php
index 5494dfd89..4c0115004 100644
--- a/dev/TestListener.php
+++ b/dev/TestListener.php
@@ -38,6 +38,18 @@ class SS_TestListener implements PHPUnit_Framework_TestListener {
$this->class->tearDownOnce();
}
+ /**
+ * Risky test.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param Exception $e
+ * @param float $time
+ * @since Method available since Release 3.8.0
+ */
+ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
+ // Stub out to support PHPUnit 3.8
+ }
+
/**
* @param String Classname
* @return boolean
From 3cfb2851227f7c9b68feef86a9b8de1da8bc5769 Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Fri, 22 Aug 2014 10:24:32 +1200
Subject: [PATCH 031/119] Reformat .travis.yml
---
.travis.yml | 24 ++++++++++--------------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index a65b00ffe..452e0ffa3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,38 +1,34 @@
language: php
php:
- - 5.3
- 5.4
- - 5.5
- - 5.6
env:
global:
+ - CORE_RELEASE=3.1
- "ARTIFACTS_AWS_REGION=us-east-1"
- "ARTIFACTS_S3_BUCKET=silverstripe-travis-artifacts"
- secure: "DjwZKhY/c0wXppGmd8oEMiTV0ayfOXiCmi9Lg1aXoSXNnj+sjLmhYwhUWjehjR6IX0MRtzJG6v7V5Y+4nSGe+i+XIrBQnhPQ95Jrkm1gKofX2mznWTl9npQElNS1DXi58NLPbiB3qxHWGFBRAWmRQrsAouyZabkPnChnSa9ldOg="
- secure: "UmbXCNLK0f2Dk+7qX8bOVcgIt4QhRvccoWvMUxaPtIU+95HCbG10eeCxvfOeBax+tHcRXmeCG4vM4tcuT/WoANkAma/VX74DylFjbWhks2tsKOcr2kjTrOwe6Q9CXOBjVAlcx0lnV/a+w83KARjXGnCrIbE7p7r4EDw31rkVufg="
matrix:
- - DB=MYSQL CORE_RELEASE=3.1
+ - DB=MYSQL
+ - DB=SQLITE
+ - DB=PGSQL
matrix:
allow_failures:
- php: hhvm-nightly
include:
- - php: 5.3
- env: DB=PGSQL CORE_RELEASE=3.1
- - php: 5.3
- env: DB=SQLITE CORE_RELEASE=3.1
- - php: 5.4
- env: DB=MYSQL CORE_RELEASE=3.1
- php: 5.5
- env: DB=MYSQL CORE_RELEASE=3.1
+ env: DB=MYSQL
- php: 5.6
- env: DB=MYSQL CORE_RELEASE=3.1
+ env: DB=MYSQL
- php: 5.4
- env: DB=MYSQL CORE_RELEASE=3.1 BEHAT_TEST=1
+ env: DB=MYSQL BEHAT_TEST=1
+ - php: 5.3
+ env: DB=MYSQL
- php: hhvm-nightly
- env: DB=MYSQL CORE_RELEASE=3.1
+ env: DB=MYSQL
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y tidy
From 8063b349c8715b341a4f1fdc1487203381e38f18 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Fri, 22 Aug 2014 14:45:44 +1200
Subject: [PATCH 032/119] BUG Fixing Director::test() failing on BASE_URL
prefixed URLs
Example: you have a site in a sub-directory off the webroot, you call
->Link() on a SiteTree record, which returns "/[sitedir]/my-page", and
you pass this URL to Director::test(). It's a valid URL, but
Director::test() will throw a 404.
Director::test() should be ensuring that all URLs passed to it are
properly made relative, not just in the case where it thinks the URL
is absolute.
---
control/Director.php | 5 ++++-
dev/FunctionalTest.php | 24 ++++++++++++++++++++++++
tests/control/HTTPTest.php | 15 ++-------------
tests/control/RequestHandlingTest.php | 18 ++++++++++++++++--
4 files changed, 46 insertions(+), 16 deletions(-)
diff --git a/control/Director.php b/control/Director.php
index b0c7cea8f..6688e3a2a 100644
--- a/control/Director.php
+++ b/control/Director.php
@@ -255,9 +255,12 @@ class Director implements TemplateGlobalProvider {
} else {
$_SERVER['HTTP_HOST'] = $bits['host'];
}
- $url = Director::makeRelative($url);
}
+ // Ensure URL is properly made relative.
+ // Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
+ $url = self::makeRelative($url);
+
$urlWithQuerystring = $url;
if(strpos($url, '?') !== false) {
list($url, $getVarsEncoded) = explode('?', $url, 2);
diff --git a/dev/FunctionalTest.php b/dev/FunctionalTest.php
index 45a8ebc3d..885f5d425 100644
--- a/dev/FunctionalTest.php
+++ b/dev/FunctionalTest.php
@@ -110,6 +110,30 @@ class FunctionalTest extends SapphireTest {
}
}
+ /**
+ * Run a test while mocking the base url with the provided value
+ * @param string $url The base URL to use for this test
+ * @param callable $callback The test to run
+ */
+ protected function withBaseURL($url, $callback) {
+ $oldBase = Config::inst()->get('Director', 'alternate_base_url');
+ Config::inst()->update('Director', 'alternate_base_url', $url);
+ $callback($this);
+ Config::inst()->update('Director', 'alternate_base_url', $oldBase);
+ }
+
+ /**
+ * Run a test while mocking the base folder with the provided value
+ * @param string $folder The base folder to use for this test
+ * @param callable $callback The test to run
+ */
+ protected function withBaseFolder($folder, $callback) {
+ $oldFolder = Config::inst()->get('Director', 'alternate_base_folder');
+ Config::inst()->update('Director', 'alternate_base_folder', $folder);
+ $callback($this);
+ Config::inst()->update('Director', 'alternate_base_folder', $oldFolder);
+ }
+
/**
* Submit a get request
* @uses Director::test()
diff --git a/tests/control/HTTPTest.php b/tests/control/HTTPTest.php
index 2d6a08e34..7baaa5e40 100644
--- a/tests/control/HTTPTest.php
+++ b/tests/control/HTTPTest.php
@@ -5,7 +5,7 @@
* @package framework
* @subpackage tests
*/
-class HTTPTest extends SapphireTest {
+class HTTPTest extends FunctionalTest {
/**
* Tests {@link HTTP::getLinksIn()}
@@ -227,16 +227,5 @@ class HTTPTest extends SapphireTest {
);
});
}
-
- /**
- * Run a test while mocking the base url with the provided value
- * @param string $url The base URL to use for this test
- * @param callable $callback The test to run
- */
- protected function withBaseURL($url, $callback) {
- $oldBase = Config::inst()->get('Director', 'alternate_base_url');
- Config::inst()->update('Director', 'alternate_base_url', $url);
- $callback($this);
- Config::inst()->update('Director', 'alternate_base_url', $oldBase);
- }
+
}
diff --git a/tests/control/RequestHandlingTest.php b/tests/control/RequestHandlingTest.php
index 99e2989bc..f1ebb780a 100644
--- a/tests/control/RequestHandlingTest.php
+++ b/tests/control/RequestHandlingTest.php
@@ -60,10 +60,24 @@ class RequestHandlingTest extends FunctionalTest {
$this->assertEquals("MyField requested", $response->getBody());
/* We can also make a POST request on a form field, which could be used for in-place editing, for example. */
- $response = Director::test("testGoodBase1/TestForm/fields/MyField" ,array("MyField" => 5));
+ $response = Director::test("testGoodBase1/TestForm/fields/MyField", array("MyField" => 5));
$this->assertEquals("MyField posted, update to 5", $response->getBody());
}
-
+
+ public function testBaseUrlPrefixed() {
+ $this->withBaseFolder('/silverstripe', function($test) {
+ $test->assertEquals(
+ 'MyField requested',
+ Director::test('/silverstripe/testGoodBase1/TestForm/fields/MyField')->getBody()
+ );
+
+ $test->assertEquals(
+ 'MyField posted, update to 5',
+ Director::test('/silverstripe/testGoodBase1/TestForm/fields/MyField', array('MyField' => 5))->getBody()
+ );
+ });
+ }
+
public function testBadBase() {
/* We no longer support using hacky attempting to handle URL parsing with broken rules */
$response = Director::test("testBadBase/method/1/2");
From 1661213e5b9ff478e19b024f5a599f50e86f2473 Mon Sep 17 00:00:00 2001
From: Ingo Schommer
Date: Fri, 31 Jan 2014 10:38:34 +1300
Subject: [PATCH 033/119] FIX Opt-out pf form message escaping (fixes #2796)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This fixes a limitation introduced through http://www.silverstripe.org/ss-2013-008-xss-in-numericfield-validation/.
Form messages used to accept HTML, now they’re escaped by default, effectively removing the ability
to pass in HTML and take care of escaping manually.
We pass through HTML to message in core through the CTF system, so this needs to be fixed.
It’s an alternative fix to https://github.com/silverstripe/silverstripe-framework/pull/2803.
---
forms/ComplexTableField.php | 12 +++----
forms/Form.php | 32 ++++++++++-------
forms/FormField.php | 9 +++--
forms/gridfield/GridFieldDetailForm.php | 18 +++++-----
security/MemberLoginForm.php | 5 ++-
tests/forms/FormTest.php | 46 +++++++++++++++++++++++++
tests/security/SecurityTest.php | 4 +--
7 files changed, 90 insertions(+), 36 deletions(-)
diff --git a/forms/ComplexTableField.php b/forms/ComplexTableField.php
index 147674aa5..44c73c3d6 100644
--- a/forms/ComplexTableField.php
+++ b/forms/ComplexTableField.php
@@ -478,7 +478,7 @@ JS;
try {
$childData->write();
} catch(ValidationException $e) {
- $form->sessionMessage($e->getResult()->message(), 'bad');
+ $form->sessionMessage($e->getResult()->message(), 'bad', false);
return Controller::curr()->redirectBack();
}
@@ -493,16 +493,16 @@ JS;
_t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
);
- $editLink = Controller::join_links($this->Link(), 'item/' . $childData->ID . '/edit');
+ $editLink = Controller::join_links($this->Link(), 'item/' . (int)$childData->ID . '/edit');
$message = _t(
'ComplexTableField.SUCCESSADD2', 'Added {name}',
array('name' => $childData->singular_name())
);
- $message .= '' . $childData->Title . '' . $closeLink;
-
- $form->sessionMessage($message, 'good');
+ $message .= '' . Convert::raw2xml($childData->Title) . '' . $closeLink;
+ $form->sessionMessage($message, 'good', false);
+
return Controller::curr()->redirectBack();
}
}
@@ -671,7 +671,7 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
$closeLink
);
- $form->sessionMessage($message, 'good');
+ $form->sessionMessage($message, 'good', false);
return Controller::curr()->redirectBack();
}
diff --git a/forms/Form.php b/forms/Form.php
index 9f3e6361f..08827275d 100644
--- a/forms/Form.php
+++ b/forms/Form.php
@@ -144,10 +144,6 @@ class Form extends RequestHandler {
*/
protected $attributes = array();
- public static $casting = array(
- 'Message' => 'Text'
- );
-
/**
* Create a new form, with the given fields an action buttons.
*
@@ -446,10 +442,10 @@ class Form extends RequestHandler {
* Add a plain text error message to a field on this form. It will be saved into the session
* and used the next time this form is displayed.
*/
- public function addErrorMessage($fieldName, $message, $messageType) {
+ public function addErrorMessage($fieldName, $message, $messageType, $escapeHtml = true) {
Session::add_to_array("FormInfo.{$this->FormName()}.errors", array(
'fieldName' => $fieldName,
- 'message' => $message,
+ 'message' => $escapeHtml ? Convert::raw2xml($message) : $message,
'messageType' => $messageType,
));
}
@@ -1015,9 +1011,12 @@ class Form extends RequestHandler {
*
* @param message the text of the message
* @param type Should be set to good, bad, or warning.
+ * @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
+ * In that case, you might want to use {@link Convert::raw2xml()} to escape any
+ * user supplied data in the message.
*/
- public function setMessage($message, $type) {
- $this->message = $message;
+ public function setMessage($message, $type, $escapeHtml = true) {
+ $this->message = ($escapeHtml) ? Convert::raw2xml($message) : $message;
$this->messageType = $type;
return $this;
}
@@ -1027,14 +1026,23 @@ class Form extends RequestHandler {
*
* @param message the text of the message
* @param type Should be set to good, bad, or warning.
+ * @param boolean $escapeHtml Automatically sanitize the message. Set to FALSE if the message contains HTML.
+ * In that case, you might want to use {@link Convert::raw2xml()} to escape any
+ * user supplied data in the message.
*/
- public function sessionMessage($message, $type) {
- Session::set("FormInfo.{$this->FormName()}.formError.message", $message);
+ public function sessionMessage($message, $type, $escapeHtml = true) {
+ Session::set(
+ "FormInfo.{$this->FormName()}.formError.message",
+ $escapeHtml ? Convert::raw2xml($message) : $message
+ );
Session::set("FormInfo.{$this->FormName()}.formError.type", $type);
}
- public static function messageForForm( $formName, $message, $type ) {
- Session::set("FormInfo.{$formName}.formError.message", $message);
+ public static function messageForForm( $formName, $message, $type, $escapeHtml = true) {
+ Session::set(
+ "FormInfo.{$formName}.formError.message",
+ $escapeHtml ? Convert::raw2xml($message) : $message
+ );
Session::set("FormInfo.{$formName}.formError.type", $type);
}
diff --git a/forms/FormField.php b/forms/FormField.php
index e0468164f..ee825cb0e 100644
--- a/forms/FormField.php
+++ b/forms/FormField.php
@@ -93,10 +93,6 @@ class FormField extends RequestHandler {
*/
protected $attributes = array();
- public static $casting = array(
- 'Message' => 'Text'
- );
-
/**
* Takes a fieldname and converts camelcase to spaced
* words. Also resolves combined fieldnames with dot syntax
@@ -471,7 +467,10 @@ class FormField extends RequestHandler {
/**
* Sets the error message to be displayed on the form field
- * Set by php validation of the form
+ * Set by php validation of the form.
+ *
+ * @param string $message Message to show to the user. Allows HTML content,
+ * which means you need to use Convert::raw2xml() for any user supplied data.
*/
public function setError($message, $messageType) {
$this->message = $message;
diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php
index 90bf9e077..d2515b62c 100644
--- a/forms/gridfield/GridFieldDetailForm.php
+++ b/forms/gridfield/GridFieldDetailForm.php
@@ -395,7 +395,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
$this->record->write();
$this->gridField->getList()->add($this->record);
} catch(ValidationException $e) {
- $form->sessionMessage($e->getResult()->message(), 'bad');
+ $form->sessionMessage($e->getResult()->message(), 'bad', false);
$responseNegotiator = new PjaxResponseNegotiator(array(
'CurrentForm' => function() use(&$form) {
return $form->forTemplate();
@@ -412,11 +412,9 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
// TODO Save this item into the given relationship
- // TODO Allow HTML in form messages
- // $link = '"'
- // . htmlspecialchars($this->record->Title, ENT_QUOTES)
- // . '"';
- $link = '"' . $this->record->Title . '"';
+ $link = '"'
+ . htmlspecialchars($this->record->Title, ENT_QUOTES)
+ . '"';
$message = _t(
'GridFieldDetailForm.Saved',
'Saved {name} {link}',
@@ -426,7 +424,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
)
);
- $form->sessionMessage($message, 'good');
+ $form->sessionMessage($message, 'good', false);
if($new_record) {
return Controller::curr()->redirect($this->Link());
@@ -453,7 +451,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
$this->record->delete();
} catch(ValidationException $e) {
- $form->sessionMessage($e->getResult()->message(), 'bad');
+ $form->sessionMessage($e->getResult()->message(), 'bad', false);
return Controller::curr()->redirectBack();
}
@@ -466,9 +464,9 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
$toplevelController = $this->getToplevelController();
if($toplevelController && $toplevelController instanceof LeftAndMain) {
$backForm = $toplevelController->getEditForm();
- $backForm->sessionMessage($message, 'good');
+ $backForm->sessionMessage($message, 'good', false);
} else {
- $form->sessionMessage($message, 'good');
+ $form->sessionMessage($message, 'good', false);
}
//when an item is deleted, redirect to the parent controller
diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php
index 5ac7b7d86..40743fb27 100644
--- a/security/MemberLoginForm.php
+++ b/security/MemberLoginForm.php
@@ -105,7 +105,6 @@ JS
* Get message from session
*/
protected function getMessageFromSession() {
- parent::getMessageFromSession();
if(($member = Member::currentUser()) && !Session::get('MemberLoginForm.force_message')) {
$this->message = _t(
'Member.LOGGEDINAS',
@@ -114,6 +113,10 @@ JS
);
}
Session::set('MemberLoginForm.force_message', false);
+
+ parent::getMessageFromSession();
+
+ return $this->message;
}
diff --git a/tests/forms/FormTest.php b/tests/forms/FormTest.php
index dbba1aa03..6d25ca411 100644
--- a/tests/forms/FormTest.php
+++ b/tests/forms/FormTest.php
@@ -395,6 +395,52 @@ class FormTest extends FunctionalTest {
$this->assertNotContains('two="2"', $form->getAttributesHTML('one', 'two'));
$this->assertContains('three="3"', $form->getAttributesHTML('one', 'two'));
}
+
+ function testMessageEscapeHtml() {
+ $form = $this->getStubForm();
+ $form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
+ $form->sessionMessage('Escaped HTML', 'good', true);
+ $parser = new CSSContentParser($form->forTemplate());
+ $messageEls = $parser->getBySelector('.message');
+ $this->assertContains(
+ '<em>Escaped HTML</em>',
+ $messageEls[0]->asXML()
+ );
+
+ $form = $this->getStubForm();
+ $form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
+ $form->sessionMessage('Unescaped HTML', 'good', false);
+ $parser = new CSSContentParser($form->forTemplate());
+ $messageEls = $parser->getBySelector('.message');
+ $this->assertContains(
+ 'Unescaped HTML',
+ $messageEls[0]->asXML()
+ );
+ }
+
+ function testFieldMessageEscapeHtml() {
+ $form = $this->getStubForm();
+ $form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
+ $form->addErrorMessage('key1', 'Escaped HTML', 'good', true);
+ $form->setupFormErrors();
+ $parser = new CSSContentParser($form->forTemplate());
+ $messageEls = $parser->getBySelector('#key1 .message');
+ $this->assertContains(
+ '<em>Escaped HTML</em>',
+ $messageEls[0]->asXML()
+ );
+
+ $form = $this->getStubForm();
+ $form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
+ $form->addErrorMessage('key1', 'Unescaped HTML', 'good', false);
+ $form->setupFormErrors();
+ $parser = new CSSContentParser($form->forTemplate());
+ $messageEls = $parser->getBySelector('#key1 .message');
+ $this->assertContains(
+ 'Unescaped HTML',
+ $messageEls[0]->asXML()
+ );
+ }
protected function getStubForm() {
return new Form(
diff --git a/tests/security/SecurityTest.php b/tests/security/SecurityTest.php
index 3325d09f9..d0e681e9d 100644
--- a/tests/security/SecurityTest.php
+++ b/tests/security/SecurityTest.php
@@ -260,7 +260,7 @@ class SecurityTest extends FunctionalTest {
/* THE FIRST 4 TIMES, THE MEMBER SHOULDN'T BE LOCKED OUT */
if($i < 5) {
$this->assertNull($member->LockedOutUntil);
- $this->assertContains($this->loginErrorMessage(), _t('Member.ERRORWRONGCRED'));
+ $this->assertContains($this->loginErrorMessage(), Convert::raw2xml(_t('Member.ERRORWRONGCRED')));
}
/* AFTER THAT THE USER IS LOCKED OUT FOR 15 MINUTES */
@@ -301,7 +301,7 @@ class SecurityTest extends FunctionalTest {
$this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword');
$this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword');
$this->assertNull($this->session()->inst_get('loggedInAs'));
- $this->assertTrue(false !== stripos($this->loginErrorMessage(), _t('Member.ERRORWRONGCRED')));
+ $this->assertContains($this->loginErrorMessage(), Convert::raw2xml(_t('Member.ERRORWRONGCRED')));
$this->doTestLoginForm('sam@silverstripe.com' , '1nitialPassword');
$this->assertEquals($this->session()->inst_get('loggedInAs'), $member->ID);
From c5956d6ac478c4b5a19ccea429b01fef8526fe2d Mon Sep 17 00:00:00 2001
From: Daniel Hensby
Date: Fri, 22 Aug 2014 15:19:01 +0100
Subject: [PATCH 034/119] Use injector to create `ValidationResult`
---
model/DataObject.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/model/DataObject.php b/model/DataObject.php
index 3f10f4a63..34d9ba93b 100644
--- a/model/DataObject.php
+++ b/model/DataObject.php
@@ -1001,7 +1001,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return A {@link ValidationResult} object
*/
protected function validate() {
- $result = new ValidationResult();
+ $result = ValidationResult::create();
$this->extend('validate', $result);
return $result;
}
From 5c9e88b9a048a5e499f420f1fe78302791270320 Mon Sep 17 00:00:00 2001
From: Ingo Schommer
Date: Sun, 24 Aug 2014 14:37:12 +1200
Subject: [PATCH 035/119] Updated contrib docs, mention "DO NOT MERGE" pull
requests
These should be avoided because they undermine the process of
peer review and merging in github, we should strive to have
zero open pull requests, as opposed to treating it as a stage
for work in progress. Intermediary code review can happen in github forks instead.
Also remove some checklist items which were based on the Trac bugtracker,
e.g. its not longer possible to assign yourself to issues because
of github's limited permission abilities.
---
docs/en/misc/contributing/code.md | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/docs/en/misc/contributing/code.md b/docs/en/misc/contributing/code.md
index 364c17f3f..4b6438a38 100644
--- a/docs/en/misc/contributing/code.md
+++ b/docs/en/misc/contributing/code.md
@@ -94,19 +94,17 @@ After you have edited the file, GitHub will offer to create a pull request for y
* Adhere to our [coding conventions](/misc/coding-conventions)
* If your patch is extensive, discuss it first on the [silverstripe-dev google group](https://groups.google.com/group/silverstripe-dev) (ideally before doing any serious coding)
-* When working on existing tickets, assign them to you and provide status updates through ticket comments
-* Check your patches against the latest "trunk" or "master", as well as the latest release.
-Please not that the latest stable release will often not be sufficient! (of all modules)
-* Provide complete [unit test coverage](/topics/testing) - depending on the complexity of your work, this is a required
-step.
-* Do not set milestones. If you think your patch should be looked at with priority, mark it as "critical".
+* When working on existing tickets, provide status updates through ticket comments
+* Check your patches against the "master" branch, as well as the latest release branch
+* Write [unit tests](/topics/testing)
+* Write [Behat integration tests](https://github.com/silverstripe-labs/silverstripe-behat-extension) for any interface changes
* Describe specifics on how to test the effects of the patch
* It's better to submit multiple patches with separate bits of functionality than a big patch containing lots of
changes
+* Only submit a pull request for work you expect to be ready to merge. Work in progress is best discussed in an issue, or on your own repository fork.
* Document your code inline through [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) syntax. See our
[API documentation](http://api.silverstripe.org/3.1/) for good examples.
-* Also check and update documentation on [doc.silverstripe.org](http://doc.silverstripe.org). Check for any references to functionality deprecated or extended through your patch. Documentation changes should be included in the patch.
-* We will attribute the change to you whereever possible (git does this automatically for pull requests)
+* Check and update documentation on [doc.silverstripe.org](http://doc.silverstripe.org). Check for any references to functionality deprecated or extended through your patch. Documentation changes should be included in the patch.
* If you get stuck, please post to the [forum](http://silverstripe.org/forum) or for deeper core problems, to the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
* When working with the CMS, please read the ["CMS Architecture Guide"](/reference/cms-architecture) first
@@ -133,7 +131,7 @@ Same goes for version control plumbing like merges, file renames or reverts.
Further guidelines:
* Each commit should form a logical unit - if you fix two unrelated bugs, commit each one separately
-* If you are fixing a ticket from our [bugtracker](http://open.silverstripe.com), please append `(fixes #)`
+* If you are fixing a issue from our bugtracker ([cms](http://github.com/silverstripe/silverstripe-framework) and [framework](http://github.com/silverstripe/silverstripe-framework)), please append `(fixes #)`
* If your change is related to another commit, reference it with its abbreviated commit hash.
* Mention important changed classes and methods in the commit summary.
From 96d0874953b1009fce4f88821c9a63c5bfc165a4 Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Wed, 12 Mar 2014 14:58:49 +1300
Subject: [PATCH 036/119] BUG Fix issue with inheritance of Injector service
configuration
---
control/injector/Injector.php | 7 ++
.../injector/ServiceConfigurationLocator.php | 12 ++-
...ilverStripeServiceConfigurationLocator.php | 76 ++++++++++++-------
tests/injector/InjectorTest.php | 58 +++++++++++---
4 files changed, 114 insertions(+), 39 deletions(-)
diff --git a/control/injector/Injector.php b/control/injector/Injector.php
index 2a7470bf7..9d1b103e7 100644
--- a/control/injector/Injector.php
+++ b/control/injector/Injector.php
@@ -180,6 +180,13 @@ class Injector {
* @var Factory
*/
protected $objectCreator;
+
+ /**
+ * Locator for determining Config properties for services
+ *
+ * @var ServiceConfigurationLocator
+ */
+ protected $configLocator;
/**
* Create a new injector.
diff --git a/control/injector/ServiceConfigurationLocator.php b/control/injector/ServiceConfigurationLocator.php
index 3bcc00404..30f171703 100644
--- a/control/injector/ServiceConfigurationLocator.php
+++ b/control/injector/ServiceConfigurationLocator.php
@@ -9,7 +9,13 @@
* @subpackage injector
*/
class ServiceConfigurationLocator {
- public function locateConfigFor($name) {
-
- }
+
+
+ /**
+ * Finds the Injector config for a named service.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function locateConfigFor($name) {}
}
\ No newline at end of file
diff --git a/control/injector/SilverStripeServiceConfigurationLocator.php b/control/injector/SilverStripeServiceConfigurationLocator.php
index faf86d755..a0a22bd25 100644
--- a/control/injector/SilverStripeServiceConfigurationLocator.php
+++ b/control/injector/SilverStripeServiceConfigurationLocator.php
@@ -7,43 +7,65 @@
* @package framework
* @subpackage injector
*/
-class SilverStripeServiceConfigurationLocator {
+class SilverStripeServiceConfigurationLocator extends ServiceConfigurationLocator {
- private $configs = array();
+ /**
+ * List of Injector configurations cached from Config in class => config format.
+ * If any config is false, this denotes that this class and all its parents
+ * have no configuration specified.
+ *
+ * @var array
+ */
+ protected $configs = array();
public function locateConfigFor($name) {
+ // Check direct or cached result
+ $config = $this->configFor($name);
+ if($config !== null) return $config;
+
+ // do parent lookup if it's a class
+ if (class_exists($name)) {
+ $parents = array_reverse(array_keys(ClassInfo::ancestry($name)));
+ array_shift($parents);
+
+ foreach ($parents as $parent) {
+ // have we already got for this?
+ $config = $this->configFor($parent);
+ if($config !== null) {
+ // Cache this result
+ $this->configs[$name] = $config;
+ return $config;
+ }
+ }
+ }
+
+ // there is no parent config, so we'll record that as false so we don't do the expensive
+ // lookup through parents again
+ $this->configs[$name] = false;
+ }
+
+ /**
+ * Retrieves the config for a named service without performing a hierarchy walk
+ *
+ * @param string $name Name of service
+ * @return mixed Returns either the configuration data, if there is any. A missing config is denoted
+ * by a value of either null (there is no direct config assigned and a hierarchy walk is necessary)
+ * or false (there is no config for this class, nor within the hierarchy for this class).
+ */
+ protected function configFor($name) {
+
+ // Return cached result
if (isset($this->configs[$name])) {
- return $this->configs[$name];
+ return $this->configs[$name]; // Potentially false
}
$config = Config::inst()->get('Injector', $name);
if ($config) {
$this->configs[$name] = $config;
return $config;
- }
-
- // do parent lookup if it's a class
- if (class_exists($name)) {
- $parents = array_reverse(array_keys(ClassInfo::ancestry($name)));
- array_shift($parents);
- foreach ($parents as $parent) {
- // have we already got for this?
- if (isset($this->configs[$parent])) {
- return $this->configs[$parent];
- }
- $config = Config::inst()->get('Injector', $parent);
- if ($config) {
- $this->configs[$name] = $config;
- return $config;
- } else {
- $this->configs[$parent] = false;
- }
- }
-
- // there is no parent config, so we'll record that as false so we don't do the expensive
- // lookup through parents again
- $this->configs[$name] = false;
+ } else {
+ return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/injector/InjectorTest.php b/tests/injector/InjectorTest.php
index 2e48fe1f0..2c3ceafb2 100644
--- a/tests/injector/InjectorTest.php
+++ b/tests/injector/InjectorTest.php
@@ -486,13 +486,39 @@ class InjectorTest extends SapphireTest {
}
public function testInheritedConfig() {
+
+ // Test top-down caching of config inheritance
$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
Config::inst()->update('Injector', 'MyParentClass', array('properties' => array('one' => 'the one')));
+ Config::inst()->update('Injector', 'MyChildClass', array('properties' => array('one' => 'the two')));
$obj = $injector->get('MyParentClass');
$this->assertEquals($obj->one, 'the one');
$obj = $injector->get('MyChildClass');
- $this->assertEquals($obj->one, 'the one');
+ $this->assertEquals($obj->one, 'the two');
+
+ $obj = $injector->get('MyGrandChildClass');
+ $this->assertEquals($obj->one, 'the two');
+
+ $obj = $injector->get('MyGreatGrandChildClass');
+ $this->assertEquals($obj->one, 'the two');
+
+ // Test bottom-up caching of config inheritance
+ $injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
+ Config::inst()->update('Injector', 'MyParentClass', array('properties' => array('one' => 'the three')));
+ Config::inst()->update('Injector', 'MyChildClass', array('properties' => array('one' => 'the four')));
+
+ $obj = $injector->get('MyGreatGrandChildClass');
+ $this->assertEquals($obj->one, 'the four');
+
+ $obj = $injector->get('MyGrandChildClass');
+ $this->assertEquals($obj->one, 'the four');
+
+ $obj = $injector->get('MyChildClass');
+ $this->assertEquals($obj->one, 'the four');
+
+ $obj = $injector->get('MyParentClass');
+ $this->assertEquals($obj->one, 'the three');
}
public function testSameNamedSingeltonPrototype() {
@@ -654,16 +680,24 @@ class InjectorTest extends SapphireTest {
}
class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly {
- public function locateConfigFor($name) {
- if ($name == 'TestObject') {
- return array('class' => 'ConstructableObject', 'constructor' => array('%$OtherTestObject'));
+
+ protected function configFor($name) {
+
+ switch($name) {
+ case 'TestObject':
+ return $this->configs[$name] = array(
+ 'class' => 'ConstructableObject',
+ 'constructor' => array('%$OtherTestObject')
+ );
+
+ case 'ConfigConstructor':
+ return $this->configs[$name] = array(
+ 'class' => 'ConstructableObject',
+ 'constructor' => array('value')
+ );
}
- if ($name == 'ConfigConstructor') {
- return array('class' => 'ConstructableObject', 'constructor' => array('value'));
- }
-
- return parent::locateConfigFor($name);
+ return parent::configFor($name);
}
}
@@ -730,6 +764,12 @@ class MyParentClass implements TestOnly {
class MyChildClass extends MyParentClass implements TestOnly {
+}
+class MyGrandChildClass extends MyChildClass implements TestOnly {
+
+}
+class MyGreatGrandChildClass extends MyGrandChildClass implements TestOnly {
+
}
class DummyRequirements implements TestOnly {
From 9da3357e3a7348914a4778366efa053277097cc4 Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Mon, 25 Aug 2014 11:25:01 +1200
Subject: [PATCH 037/119] add 3.1.6 docs
---
docs/en/changelogs/3.1.6.md | 59 +++++++++++++++++++++++++++++++++++++
docs/en/changelogs/index.md | 4 ++-
2 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/docs/en/changelogs/3.1.6.md b/docs/en/changelogs/3.1.6.md
index 54d68db30..c7b6c1aaf 100644
--- a/docs/en/changelogs/3.1.6.md
+++ b/docs/en/changelogs/3.1.6.md
@@ -7,3 +7,62 @@
follows the same process as the use of the 'flush' query string parameter, and will redirect
requests containing this argument with a token in the querystring.
Director::isDev, Director::isTest, and Director::isLive no longer have any parameters.
+
+### API Changes
+
+ * 2014-07-28 [0e78e3f](https://github.com/silverstripe/sapphire/commit/0e78e3f) Let update interval of tinymce be changed or disabled (Damian Mooyman)
+ * 2014-07-21 [4453caf](https://github.com/silverstripe/sapphire/commit/4453caf) Let extensions control folder selector in HtmlEditorField_Toolbar (Damian Mooyman)
+ * 2014-07-05 [3c5e51a](https://github.com/silverstripe/sapphire/commit/3c5e51a) Debug::dump in CLI no longer generates HTML. Uses colours. API Column size is configurable in DebugView (Damian Mooyman)
+ * 2014-05-22 [ec325a3](https://github.com/silverstripe/sapphire/commit/ec325a3) Fix HTTPS proxy header detection (Ingo Schommer)
+
+### Features and Enhancements
+
+ * 2014-08-13 [5704ae2](https://github.com/silverstripe/sapphire/commit/5704ae2) Sanitise task name in runTask (Kirk Mayo)
+ * 2014-07-28 [482c23f](https://github.com/silverstripe/silverstripe-cms/commit/482c23f) Adding CMS sitetree filter to see the current 'live' site (Stig Lindqvist)
+ * 2014-06-28 [1d86fe4](https://github.com/silverstripe/sapphire/commit/1d86fe4) allow force resampling on images (Stevie Mayhew)
+ * 2014-07-28 [ac95a87](https://github.com/silverstripe/sapphire/commit/ac95a87) Allow configuring Image backend via yaml. (Jeremy Shipman)
+
+### Bugfixes
+
+ * 2014-08-15 [7e70b8d](https://github.com/silverstripe/sapphire/commit/7e70b8d) Reapply fix for cms crashing due to History.js blindly appending slashes to end of url (Damian Mooyman)
+ * 2014-05-15 [7277dc1](https://github.com/silverstripe/silverstripe-cms/commit/7277dc1) Fix sorting on main ReportAdmin grid ref: CWPBUG-133 (Damian Mooyman)
+ * 2014-06-01 [e535c35](https://github.com/silverstripe/sapphire/commit/e535c35) New JS sprintf and inject replacement functions (colymba)
+ * 2014-08-11 [98907fb](https://github.com/silverstripe/sapphire/commit/98907fb) Fix incorrect parsing of HTML content (Damian Mooyman)
+ * 2014-08-08 [a369094](https://github.com/silverstripe/sapphire/commit/a369094) Fix issue with generating tree data for missing pages (Damian Mooyman)
+ * 2014-08-06 [53dbbb7](https://github.com/silverstripe/silverstripe-cms/commit/53dbbb7) Fix CMSMain::getList to correctly respect filter result Fixes #1064 CMSSiteTreeFilter refactored to allow SS_List of filtered pages to be returned (Damian Mooyman)
+ * 2014-08-06 [1c48cb6](https://github.com/silverstripe/silverstripe-cms/commit/1c48cb6) Fix search range for asset filter (Damian Mooyman)
+ * 2014-06-27 [f19b1ee](https://github.com/silverstripe/sapphire/commit/f19b1ee) declarations matching PHPUnit_Framework_Assert (Michael Parkhill)
+ * 2014-08-04 [b2dac64](https://github.com/silverstripe/sapphire/commit/b2dac64) Fixed escaping of name/value in options of form fields (Sean Harvey)
+ * 2014-08-01 [9281089](https://github.com/silverstripe/sapphire/commit/9281089) Return the promise instead of the whole deferred object. (Mateusz Uzdowski)
+ * 2014-07-31 [d8302a0](https://github.com/silverstripe/sapphire/commit/d8302a0) Add a synthetic event to workaround IE8 issues. (Mateusz Uzdowski)
+ * 2014-07-30 [31c9fb5](https://github.com/silverstripe/sapphire/commit/31c9fb5) Fix the anchor selector to work for internal pages. (Mateusz Uzdowski)
+ * 2014-07-29 [baa2b69](https://github.com/silverstripe/sapphire/commit/baa2b69) Fixing incorrect error message on failed UploadField upload (Sean Harvey)
+ * 2014-07-29 [329dffd](https://github.com/silverstripe/sapphire/commit/329dffd) AssetUploadField hides "generic" file upload messages. (Sean Harvey)
+ * 2014-07-28 [62ed2d0](https://github.com/silverstripe/sapphire/commit/62ed2d0) Fix periodic tinymce layout refresh (Damian Mooyman)
+ * 2014-07-24 [3eefd65](https://github.com/silverstripe/silverstripe-cms/commit/3eefd65) Narrowing site tree search to one date shows no pages (Stig Lindqvist)
+ * 2014-07-20 [333a2aa](https://github.com/silverstripe/sapphire/commit/333a2aa) CMS tree filters doesn't count the correct number of children for deleted pages (Stig Lindqvist)
+ * 2014-07-17 [ac64d25](https://github.com/silverstripe/sapphire/commit/ac64d25) If user is logged out getHtmlEditorConfigForCMS() gets called on non object (Stig Lindqvist)
+ * 2014-07-17 [df6a8b6](https://github.com/silverstripe/sapphire/commit/df6a8b6) #3282: Added ability to subselect with in left or inner join (Senorgeno)
+ * 2014-07-14 [b34aaca](https://github.com/silverstripe/sapphire/commit/b34aaca) Fix several issues around onmatch/onunmatch entwines. (Mateusz Uzdowski)
+ * 2014-07-03 [c329f07](https://github.com/silverstripe/sapphire/commit/c329f07) Fix incorrect common_languages config (Damian Mooyman)
+ * 2014-07-01 [d3c7e41](https://github.com/silverstripe/sapphire/commit/d3c7e41) using isDev or isTest query string no longer triggers basic auth (Damian Mooyman)
+ * 2014-07-01 [a777266](https://github.com/silverstripe/silverstripe-cms/commit/a777266) ensure controller stack is updated when execution halted by an exception. (Will Rossiter)
+ * 2014-06-28 [2c741fe](https://github.com/silverstripe/sapphire/commit/2c741fe) Add support for compositedbfield within many_many_extraFields (Will Rossiter)
+ * 2014-06-16 [3d71a22](https://github.com/silverstripe/sapphire/commit/3d71a22) ClassManifest errors if files contain duplicate class names (fixes #3210) (Loz Calver)
+ * 2014-06-12 [d516063](https://github.com/silverstripe/sapphire/commit/d516063) fix dependency injection stumbling over ViewableData's __isset (Damian Mooyman)
+ * 2014-06-11 [18b6870](https://github.com/silverstripe/sapphire/commit/18b6870) Sanitise the PHP output. (Mateusz Uzdowski)
+ * 2014-06-10 [1e19485](https://github.com/silverstripe/silverstripe-cms/commit/1e19485) Ensure that all child pages are deleted (regardless of ShowInMenu status) under enforce_strict_hierarchy. (Rodney Way)
+ * 2014-05-30 [b8d19ba](https://github.com/silverstripe/silverstripe-cms/commit/b8d19ba) Fix deleted pages redirecting the CMS Update behat tests for Mink 1.6 compatibility (Damian Mooyman)
+ * 2014-05-22 [f9e7d47](https://github.com/silverstripe/sapphire/commit/f9e7d47) fix listview not working with IE9 (Igor)
+ * 2014-05-20 [4a34c36](https://github.com/silverstripe/sapphire/commit/4a34c36) Fix access to protected Session::current_session() Fixes #3144 (Damian Mooyman)
+ * 2014-05-15 [c24a2c2](https://github.com/silverstripe/sapphire/commit/c24a2c2) ArrayList failing to respect the SS_Sortable interface ref: CWPBUG-133 (Damian Mooyman)
+ * 2014-05-15 [ee6e496](https://github.com/silverstripe/sapphire/commit/ee6e496) Fix grid field showing search without search component added ref: CWPBUG-133 (Damian Mooyman)
+ * 2014-05-12 [51c3346](https://github.com/silverstripe/sapphire/commit/51c3346) Fix deprecated use of statics in test cases (Damian Mooyman)
+ * 2014-05-10 [d012b79](https://github.com/silverstripe/sapphire/commit/d012b79) Prevent i18n clearing all SS_Caches (fixes #3122) (Loz Calver)
+ * 2014-05-02 [807755f](https://github.com/silverstripe/sapphire/commit/807755f) TemplateManifest prevent cache collision (Will Morgan)
+
+## Changelog
+
+ * [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.6)
+ * [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.6)
+ * [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.6)
diff --git a/docs/en/changelogs/index.md b/docs/en/changelogs/index.md
index a49a54373..9b15e7b6c 100644
--- a/docs/en/changelogs/index.md
+++ b/docs/en/changelogs/index.md
@@ -9,6 +9,7 @@ For information on how to upgrade to newer versions consult the [upgrading](/ins
## Stable Releases
+ * [3.1.6](3.1.6) - 25 August 2014
* [3.1.5](3.1.5) - 13 May 2014
* [3.1.4](3.1.4) - 8 April 2014
* [3.1.0](3.1.0) - 1 October 2013
@@ -77,8 +78,9 @@ For information on how to upgrade to newer versions consult the [upgrading](/ins
* [2.0.1](2.0.1) - 17 April 2007
* 2.0.0 - 3 February 2007 (initial release)
-## Alpha/beta/release candidate ##
+## Alpha/beta/release candidate
+ * [3.1.6-rc3](rc/3.1.6-rc3) - 18 August 2014
* [3.1.6-rc2](rc/3.1.6-rc2) - 12 August 2014
* [3.1.6-rc1](rc/3.1.6-rc1) - 5 August 2014
* [3.1.5-rc1](rc/3.1.5-rc1) - 7 May 2014
From 70dfc55d34ce69c7d42c7d9c2f35c6fabde485c1 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Tue, 26 Aug 2014 14:55:28 +1200
Subject: [PATCH 038/119] Minor correction to documentation
---
docs/en/reference/flushable.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/en/reference/flushable.md b/docs/en/reference/flushable.md
index b24634c57..9f2c61815 100644
--- a/docs/en/reference/flushable.md
+++ b/docs/en/reference/flushable.md
@@ -26,7 +26,7 @@ This example uses `[api:SS_Cache]` in some custom code, and the same cache is cl
public function MyCachedContent() {
$cache = SS_Cache::factory('mycache')
- $something = $cache->get('mykey');
+ $something = $cache->load('mykey');
if(!$something) {
$something = 'value to be cached';
$cache->save($something);
From 7993875f16b7eb16b362dacf1b2ca9da9bd28cff Mon Sep 17 00:00:00 2001
From: Will Rossiter
Date: Tue, 26 Aug 2014 14:56:45 +1200
Subject: [PATCH 039/119] FIX: Sorting a DataQuery over a relation.
When sorting a DataQuery over a relation, the SQLQuery automatically included the sort column. The issue with the implement is that potentially the joined record has a field with the same name as the source record causing it to be overridden.
In the attached test case, without the patch the title will be set to 'Bar' rather than 'Foo'.
This patch aliases the sort column. Alternativally a patch would be to
---
model/DataQuery.php | 19 ++++++++++++++-----
tests/model/DataQueryTest.php | 25 ++++++++++++++++++++++++-
2 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/model/DataQuery.php b/model/DataQuery.php
index 089f275ca..8b5b5e612 100644
--- a/model/DataQuery.php
+++ b/model/DataQuery.php
@@ -256,6 +256,7 @@ class DataQuery {
if($orderby = $query->getOrderBy()) {
$newOrderby = array();
+ $i = 0;
foreach($orderby as $k => $dir) {
$newOrderby[$k] = $dir;
@@ -268,7 +269,10 @@ class DataQuery {
// Pull through SortColumn references from the originalSelect variables
if(preg_match('/_SortColumn/', $col)) {
- if(isset($originalSelect[$col])) $query->selectField($originalSelect[$col], $col);
+ if(isset($originalSelect[$col])) {
+ $query->selectField($originalSelect[$col], $col);
+ }
+
continue;
}
@@ -287,6 +291,7 @@ class DataQuery {
// remove original sort
unset($newOrderby[$k]);
+
// add new columns sort
$newOrderby[$qualCol] = $dir;
@@ -298,13 +303,17 @@ class DataQuery {
}
} else {
$qualCol = '"' . implode('"."', $parts) . '"';
-
- // To-do: Remove this if block once SQLQuery::$select has been refactored to store getSelect()
- // format internally; then this check can be part of selectField()
+
if(!in_array($qualCol, $query->getSelect())) {
- $query->selectField($qualCol);
+ unset($newOrderby[$k]);
+
+ $newOrderby["\"_SortColumn$i\""] = $dir;
+ $query->selectField($qualCol, "_SortColumn$i");
+
+ $i++;
}
}
+
}
$query->setOrderBy($newOrderby);
diff --git a/tests/model/DataQueryTest.php b/tests/model/DataQueryTest.php
index 09454c044..b76f6fdd1 100644
--- a/tests/model/DataQueryTest.php
+++ b/tests/model/DataQueryTest.php
@@ -12,6 +12,27 @@ class DataQueryTest extends SapphireTest {
'DataQueryTest_E',
);
+
+ public function testSortByJoinedFieldRetainsSourceInformation() {
+ $bar = new DataQueryTest_C();
+ $bar->Title = "Bar";
+ $bar->write();
+
+ $foo = new DataQueryTest_B();
+ $foo->Title = "Foo";
+ $foo->TestC = $bar->ID;
+ $foo->write();
+
+ $query = new DataQuery('DataQueryTest_B');
+ $result = $query->leftJoin(
+ 'DataQueryTest_C',
+ "\"DataQueryTest_B\".\"TestCID\" = \"DataQueryTest_B\".\"ID\""
+ )->sort('"DataQueryTest_B"."Title"', 'ASC');
+
+ $result = $result->execute()->record();
+ $this->assertEquals('Foo', $result['Title']);
+ }
+
/**
* Test the leftJoin() and innerJoin method of the DataQuery object
*/
@@ -138,6 +159,7 @@ class DataQueryTest extends SapphireTest {
class DataQueryTest_A extends DataObject implements TestOnly {
+
private static $db = array(
'Name' => 'Varchar',
);
@@ -147,7 +169,8 @@ class DataQueryTest_A extends DataObject implements TestOnly {
);
}
-class DataQueryTest_B extends DataQueryTest_A {
+class DataQueryTest_B extends DataObject implements TestOnly {
+
private static $db = array(
'Title' => 'Varchar',
);
From 298017c6d1266233fcaf649a109e181b07d4d2af Mon Sep 17 00:00:00 2001
From: Cam Spiers
Date: Thu, 28 Aug 2014 14:26:17 +1200
Subject: [PATCH 040/119] Fix deprecation warnings in internal SQLQuery usage
---
model/SQLQuery.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/model/SQLQuery.php b/model/SQLQuery.php
index c4a12cad1..2965d062b 100644
--- a/model/SQLQuery.php
+++ b/model/SQLQuery.php
@@ -999,8 +999,8 @@ class SQLQuery {
if($column == null) {
if($this->groupby) {
$countQuery = new SQLQuery();
- $countQuery->select("count(*)");
- $countQuery->from = array('(' . $clone->sql() . ') all_distinct');
+ $countQuery->setSelect("count(*)");
+ $countQuery->setFrom('(' . $clone->sql() . ') all_distinct');
return $countQuery->execute()->value();
From 64490129c2609f70be166661238fd9c00394f6d0 Mon Sep 17 00:00:00 2001
From: "Elvinas L."
Date: Fri, 29 Aug 2014 11:04:44 +0300
Subject: [PATCH 041/119] RSS feed should be sent with charset=utf-8 header
---
api/RSSFeed.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/api/RSSFeed.php b/api/RSSFeed.php
index df0ce0d26..acc81fb02 100644
--- a/api/RSSFeed.php
+++ b/api/RSSFeed.php
@@ -201,7 +201,7 @@ class RSSFeed extends ViewableData {
if(!headers_sent()) {
HTTP::add_cache_headers();
- $response->addHeader("Content-Type", "application/rss+xml");
+ $response->addHeader("Content-Type", "application/rss+xml; charset=utf-8");
}
Config::inst()->update('SSViewer', 'source_file_comments', $prevState);
From 6141930f1588de1a1cb411a5aae1ce28c4ff0c51 Mon Sep 17 00:00:00 2001
From: Nik
Date: Tue, 2 Sep 2014 15:16:20 +1200
Subject: [PATCH 042/119] Copy SSViewer rewrite_hash_links regexp to
SSTemplateParser As this is meant to represent the same functionality in two
different areas, it should behave the same way in both.
---
view/SSTemplateParser.php | 4 ++--
view/SSTemplateParser.php.inc | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/view/SSTemplateParser.php b/view/SSTemplateParser.php
index 1abe84845..3c11980dc 100644
--- a/view/SSTemplateParser.php
+++ b/view/SSTemplateParser.php
@@ -4682,8 +4682,8 @@ class SSTemplateParser extends Parser implements TemplateParser {
// TODO: This is pretty ugly & gets applied on all files not just html. I wonder if we can make this
// non-dynamically calculated
$text = preg_replace(
- '/href\s*\=\s*\"\#/',
- 'href="\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
+ '/(]+href *= *)"#/i',
+ '\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") .
\'#',
$text
diff --git a/view/SSTemplateParser.php.inc b/view/SSTemplateParser.php.inc
index 255482aae..74fc27bbe 100644
--- a/view/SSTemplateParser.php.inc
+++ b/view/SSTemplateParser.php.inc
@@ -1136,8 +1136,8 @@ class SSTemplateParser extends Parser implements TemplateParser {
// TODO: This is pretty ugly & gets applied on all files not just html. I wonder if we can make this
// non-dynamically calculated
$text = preg_replace(
- '/href\s*\=\s*\"\#/',
- 'href="\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
+ '/(]+href *= *)"#/i',
+ '\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") .
\'#',
$text
From 56d84d22a166b841420d7917ecbe68691b9d8535 Mon Sep 17 00:00:00 2001
From: Damian Mooyman
Date: Wed, 3 Sep 2014 11:43:24 +1200
Subject: [PATCH 043/119] BUG MySQLDatabase performs queries on wrong DB
connection when using connection $name != 'default'
---
model/MySQLDatabase.php | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/model/MySQLDatabase.php b/model/MySQLDatabase.php
index 8e4ce3382..b15ec04bc 100644
--- a/model/MySQLDatabase.php
+++ b/model/MySQLDatabase.php
@@ -396,7 +396,7 @@ class MySQLDatabase extends SS_Database {
private static $_cache_collation_info = array();
public function fieldList($table) {
- $fields = DB::query("SHOW FULL FIELDS IN \"$table\"");
+ $fields = $this->query("SHOW FULL FIELDS IN \"$table\"");
foreach($fields as $field) {
// ensure that '' is converted to \' in field specification (mostly for the benefit of ENUM values)
@@ -409,7 +409,7 @@ class MySQLDatabase extends SS_Database {
// Cache collation info to cut down on database traffic
if(!isset(self::$_cache_collation_info[$field['Collation']])) {
self::$_cache_collation_info[$field['Collation']]
- = DB::query("SHOW COLLATION LIKE '$field[Collation]'")->record();
+ = $this->query("SHOW COLLATION LIKE '$field[Collation]'")->record();
}
$collInfo = self::$_cache_collation_info[$field['Collation']];
$fieldSpec .= " character set $collInfo[Charset] collate $field[Collation]";
@@ -537,7 +537,7 @@ class MySQLDatabase extends SS_Database {
* @return array
*/
public function indexList($table) {
- $indexes = DB::query("SHOW INDEXES IN \"$table\"");
+ $indexes = $this->query("SHOW INDEXES IN \"$table\"");
$groupedIndexes = array();
$indexList = array();
@@ -817,7 +817,7 @@ class MySQLDatabase extends SS_Database {
*/
public function enumValuesForField($tableName, $fieldName) {
// Get the enum of all page types from the SiteTree table
- $classnameinfo = DB::query("DESCRIBE \"$tableName\" \"$fieldName\"")->first();
+ $classnameinfo = $this->query("DESCRIBE \"$tableName\" \"$fieldName\"")->first();
preg_match_all("/'[^,]+'/", $classnameinfo["Type"], $matches);
$classes = array();
@@ -927,7 +927,7 @@ class MySQLDatabase extends SS_Database {
$fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy LIMIT $limit";
// Get records
- $records = DB::query($fullQuery);
+ $records = $this->query($fullQuery);
$objects = array();
@@ -1194,7 +1194,7 @@ class MySQLDatabase extends SS_Database {
public function canLock($name) {
$id = $this->getLockIdentifier($name);
- return (bool)DB::query(sprintf("SELECT IS_FREE_LOCK('%s')", $id))->value();
+ return (bool)$this->query(sprintf("SELECT IS_FREE_LOCK('%s')", $id))->value();
}
public function getLock($name, $timeout = 5) {
@@ -1203,12 +1203,12 @@ class MySQLDatabase extends SS_Database {
// MySQL auto-releases existing locks on subsequent GET_LOCK() calls,
// in contrast to PostgreSQL and SQL Server who stack the locks.
- return (bool)DB::query(sprintf("SELECT GET_LOCK('%s', %d)", $id, $timeout))->value();
+ return (bool)$this->query(sprintf("SELECT GET_LOCK('%s', %d)", $id, $timeout))->value();
}
public function releaseLock($name) {
$id = $this->getLockIdentifier($name);
- return (bool)DB::query(sprintf("SELECT RELEASE_LOCK('%s')", $id))->value();
+ return (bool)$this->query(sprintf("SELECT RELEASE_LOCK('%s')", $id))->value();
}
protected function getLockIdentifier($name) {
From e93683bfca0be3a2228ba6ce4abe50c06c5e78f9 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Wed, 3 Sep 2014 16:24:56 +1200
Subject: [PATCH 044/119] showqueries output incorrectly shows "ms" instead of
"s"
---
model/MySQLDatabase.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/model/MySQLDatabase.php b/model/MySQLDatabase.php
index b15ec04bc..2bd526880 100644
--- a/model/MySQLDatabase.php
+++ b/model/MySQLDatabase.php
@@ -143,7 +143,7 @@ class MySQLDatabase extends SS_Database {
if(isset($_REQUEST['showqueries']) && Director::isDev(true)) {
$endtime = round(microtime(true) - $starttime,4);
- Debug::message("\n$sql\n{$endtime}ms\n", false);
+ Debug::message("\n$sql\n{$endtime}s\n", false);
}
if(!$handle && $errorLevel) {
@@ -1215,4 +1215,4 @@ class MySQLDatabase extends SS_Database {
// Prefix with database name
return Convert::raw2sql($this->database . '_' . Convert::raw2sql($name));
}
-}
\ No newline at end of file
+}
From fe42abce694d138a332dec65f2a35ece42d49930 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Wed, 3 Sep 2014 23:02:04 +1200
Subject: [PATCH 045/119] BUG CSSContentParser fails if CLI tidy doesn't output
anything.
In cases where CLI tidy fails to recognise HTML with errors or
unknown HTML5 elements, it doesn't output any content and
CSSContentParser fails.
This adds the --force-output parameter so that it always outputs
the input content, even if it wasn't completely fixed by tidy.
It seems that tidy class in PHP already does this, so this just
fixes the CLI use of tidy in CSSContentParser to be consistent.
This fix can be observed on the HHVM travis builds, as tidy isn't
compiled into HHVM as an extension.
---
dev/CSSContentParser.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev/CSSContentParser.php b/dev/CSSContentParser.php
index 06b8a0770..d68360458 100644
--- a/dev/CSSContentParser.php
+++ b/dev/CSSContentParser.php
@@ -41,7 +41,7 @@ class CSSContentParser extends Object {
} elseif(@shell_exec('which tidy')) {
// using tiny through cli
$CLI_content = escapeshellarg($content);
- $tidy = `echo $CLI_content | tidy -n -q -utf8 -asxhtml -w 0 2> /dev/null`;
+ $tidy = `echo $CLI_content | tidy --force-output 1 -n -q -utf8 -asxhtml -w 0 2> /dev/null`;
$tidy = str_replace('xmlns="http://www.w3.org/1999/xhtml"','',$tidy);
$tidy = str_replace(' ','',$tidy);
} else {
From 05379ff188b3bc7cd331c17194b71f91df6240c5 Mon Sep 17 00:00:00 2001
From: Stevie Mayhew
Date: Thu, 4 Sep 2014 09:16:13 +1200
Subject: [PATCH 046/119] DataList spelling error
Minor spelling error in DataList
---
model/DataList.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/model/DataList.php b/model/DataList.php
index 638dd3b3a..ff9f92b7e 100644
--- a/model/DataList.php
+++ b/model/DataList.php
@@ -310,7 +310,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
*
* @todo extract the sql from $customQuery into a SQLGenerator class
*
- * @param string|array Key and Value pairs, the array values are automatically sanitised for the DB quesry
+ * @param string|array Key and Value pairs, the array values are automatically sanitised for the DB query
* @return DataList
*/
public function filter() {
From 400bea1ee12a469f27ab7e41137a19a22e841089 Mon Sep 17 00:00:00 2001
From: spekulatius
Date: Thu, 4 Sep 2014 10:51:02 +1200
Subject: [PATCH 047/119] Update templates.md
Remove link to page which wasn't continued since 2.4.
---
docs/en/reference/templates.md | 2 --
1 file changed, 2 deletions(-)
diff --git a/docs/en/reference/templates.md b/docs/en/reference/templates.md
index da8f0a19a..1311bd120 100644
--- a/docs/en/reference/templates.md
+++ b/docs/en/reference/templates.md
@@ -705,8 +705,6 @@ width independently.
* Break down your templates into groups of includes. Site integrators would then have the power to override individual
includes, rather than entire templates.
-For more information about templates go to the [Advanced Templates](/reference/advanced-templates) page.
-
## Related
* [Built in page controls](/reference/built-in-page-controls)
From 151b7e9876512d17ad1aef847e87f422c927bbcf Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Wed, 3 Sep 2014 18:53:47 +1200
Subject: [PATCH 048/119] Adding ability to change query distinct on DataList
and DataQuery
---
model/DataList.php | 13 ++++++++++++-
model/DataQuery.php | 11 +++++++++++
tests/model/DataListTest.php | 13 ++++++++++++-
tests/model/DataQueryTest.php | 12 ++++++++++++
4 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/model/DataList.php b/model/DataList.php
index 638dd3b3a..96f89e1ea 100644
--- a/model/DataList.php
+++ b/model/DataList.php
@@ -223,7 +223,18 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
$query->limit($limit, $offset);
});
}
-
+
+ /**
+ * Return a new DataList instance with distinct records or not
+ *
+ * @param bool $value
+ */
+ public function distinct($value) {
+ return $this->alterDataQuery(function($query) use ($value){
+ $query->distinct($value);
+ });
+ }
+
/**
* Return a new DataList instance as a copy of this data list with the sort
* order set.
diff --git a/model/DataQuery.php b/model/DataQuery.php
index 8b5b5e612..801546974 100644
--- a/model/DataQuery.php
+++ b/model/DataQuery.php
@@ -549,6 +549,17 @@ class DataQuery {
return $this;
}
+ /**
+ * Set whether this query should be distinct or not.
+ *
+ * @param bool $value
+ * @return DataQuery
+ */
+ public function distinct($value) {
+ $this->query->setDistinct($value);
+ return $this;
+ }
+
/**
* Add an INNER JOIN clause to this query.
*
diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php
index 2c647260a..cf907c86d 100755
--- a/tests/model/DataListTest.php
+++ b/tests/model/DataListTest.php
@@ -121,7 +121,18 @@ class DataListTest extends SapphireTest {
// $check = $list->limit(null, 2);
// $this->assertEquals(1, $check->count());
}
-
+
+ public function testDistinct() {
+ $list = DataObjectTest_TeamComment::get();
+ $this->assertContains('SELECT DISTINCT', $list->dataQuery()->sql(), 'Query is set as distinct by default');
+
+ $list = $list->distinct(false);
+ $this->assertNotContains('SELECT DISTINCT', $list->dataQuery()->sql(), 'Query does not contain distinct');
+
+ $list = $list->distinct(true);
+ $this->assertContains('SELECT DISTINCT', $list->dataQuery()->sql(), 'Query contains distinct');
+ }
+
public function testDataClass() {
$list = DataObjectTest_TeamComment::get();
$this->assertEquals('DataObjectTest_TeamComment',$list->dataClass());
diff --git a/tests/model/DataQueryTest.php b/tests/model/DataQueryTest.php
index b76f6fdd1..f29d8a8d4 100644
--- a/tests/model/DataQueryTest.php
+++ b/tests/model/DataQueryTest.php
@@ -155,6 +155,18 @@ class DataQueryTest extends SapphireTest {
$result = $query->column('Title');
$this->assertEquals(array('First', 'Second', 'Last'), $result);
}
+
+ public function testDistinct() {
+ $query = new DataQuery('DataQueryTest_E');
+ $this->assertContains('SELECT DISTINCT', $query->sql(), 'Query is set as distinct by default');
+
+ $query = $query->distinct(false);
+ $this->assertNotContains('SELECT DISTINCT', $query->sql(), 'Query does not contain distinct');
+
+ $query = $query->distinct(true);
+ $this->assertContains('SELECT DISTINCT', $query->sql(), 'Query contains distinct');
+ }
+
}
From 2d0fe7a4aa347350ba1211bf1727c62eeb08165a Mon Sep 17 00:00:00 2001
From: AntonyThorpe
Date: Mon, 8 Sep 2014 17:46:09 +1200
Subject: [PATCH 049/119] Update PhpUnitWrapper.php & TestRunner.php to
recommend PHPUnit installation via Composer rather than PEAR
Using the PEAR Installer to install PHPUnit is no longer supported. See https://github.com/sebastianbergmann/phpunit/wiki/End-of-Life-for-PEAR-Installation-Method.
Replace PEAR in a comment and die output message with Composer.
Signed-off-by: AntonyThorpe
---
dev/TestRunner.php | 2 +-
dev/phpunit/PhpUnitWrapper.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/dev/TestRunner.php b/dev/TestRunner.php
index 8cd57b6fe..b2b81d787 100755
--- a/dev/TestRunner.php
+++ b/dev/TestRunner.php
@@ -112,7 +112,7 @@ class TestRunner extends Controller {
if (!self::$default_reporter) self::set_reporter(Director::is_cli() ? 'CliDebugView' : 'DebugView');
if(!PhpUnitWrapper::has_php_unit()) {
- die("Please install PHPUnit using pear");
+ die("Please install PHPUnit using Composer");
}
}
diff --git a/dev/phpunit/PhpUnitWrapper.php b/dev/phpunit/PhpUnitWrapper.php
index 3765d6478..9d4c3000b 100644
--- a/dev/phpunit/PhpUnitWrapper.php
+++ b/dev/phpunit/PhpUnitWrapper.php
@@ -260,7 +260,7 @@ class PhpUnitWrapper_Exception extends Exception {}
//
if(!PhpUnitWrapper::has_php_unit()) {
/**
- * PHPUnit is a testing framework that can be installed using PEAR.
+ * PHPUnit is a testing framework that can be installed using Composer.
* It's not bundled with SilverStripe, you will need to install it yourself.
*
* @package framework
From aa8c4fd004cc8c921e67adfdc77275492c705eb3 Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Mon, 8 Sep 2014 23:34:33 +1200
Subject: [PATCH 050/119] Adding some README badges for downloads, license and
stable version.
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 74905b374..cb14db836 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
## SilverStripe Framework
-[![Build Status](https://secure.travis-ci.org/silverstripe/silverstripe-framework.png?branch=3.1)](https://travis-ci.org/silverstripe/silverstripe-framework)
+[![Build Status](https://api.travis-ci.org/silverstripe/silverstripe-framework.svg?branch=3.1)](https://travis-ci.org/silverstripe/silverstripe-framework)
+[![Latest Stable Version](https://poser.pugx.org/silverstripe/framework/version.svg)](http://www.silverstripe.org/stable-download/)
+[![Latest Unstable Version](https://poser.pugx.org/silverstripe/framework/v/unstable.svg)](https://packagist.org/packages/silverstripe/framework)
+[![Total Downloads](https://poser.pugx.org/silverstripe/framework/downloads.svg)](https://packagist.org/packages/silverstripe/framework)
+[![License](https://poser.pugx.org/silverstripe/framework/license.svg)](https://github.com/silverstripe/silverstripe-framework#license)
PHP5 framework forming the base for the SilverStripe CMS ([http://silverstripe.org](http://silverstripe.org)).
Requires a [`silverstripe-installer`](http://github.com/silverstripe/silverstripe-installer) base project. Typically used alongside the [`cms`](http://github.com/silverstripe/silverstripe-cms) module.
From 6267d2e51ef5053c4c0cca99b406e845203d335e Mon Sep 17 00:00:00 2001
From: Sean Harvey
Date: Tue, 9 Sep 2014 11:14:38 +1200
Subject: [PATCH 051/119] Fixing broken docs on setting allowed extensions on a
FileField
Refers to an invalid variable, and is overriding the entire Upload,
which isn't necessary.
---
docs/en/topics/forms.md | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/docs/en/topics/forms.md b/docs/en/topics/forms.md
index cb0c902a3..4486c8c25 100644
--- a/docs/en/topics/forms.md
+++ b/docs/en/topics/forms.md
@@ -534,10 +534,8 @@ own validation.
// Limit extensions on upload (in PHP)
// Markup contains
$exts = array('jpg', 'jpeg', 'gif');
- $validator = new Upload_Validator();
- $validator->setAllowedExtensions($exts);
- $upload = Upload::create()->setValidator($validator);
- $fileField = FileField::create('MyFile')->setUpload(new);
+ $fileField = FileField::create('MyFile');
+ $fileField->getValidator()->setAllowedExtensions($exts);
$fileField->setAttribute('data-allowed-extensions', implode(',', $exts));
Note that these examples don't have any effect on the client as such, but are
From 3eabd7d41af45f3f22130339496fa37325478bcf Mon Sep 17 00:00:00 2001
From: Juri Demeschew
Date: Sat, 30 Aug 2014 20:32:28 +0200
Subject: [PATCH 052/119] Fix #3356 js not properly included
---
tests/forms/RequirementsTest.php | 22 ++++++++++++++++++++++
view/Requirements.php | 14 ++++++++++++--
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/tests/forms/RequirementsTest.php b/tests/forms/RequirementsTest.php
index 3c1e4b4ad..70a30c8f3 100644
--- a/tests/forms/RequirementsTest.php
+++ b/tests/forms/RequirementsTest.php
@@ -350,6 +350,28 @@ class RequirementsTest extends SapphireTest {
$this->assertContains('
', $html);
}
+ public function testIncludedJsIsNotCommentedOut() {
+ $template = '