diff --git a/.travis.yml b/.travis.yml index 23ecede32..031809900 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ matrix: env: DB=MYSQL CORE_RELEASE=master - php: 5.5 env: DB=MYSQL CORE_RELEASE=master + - php: 5.6 + env: DB=MYSQL CORE_RELEASE=master - php: 5.4 env: DB=MYSQL CORE_RELEASE=master BEHAT_TEST=1 diff --git a/cache/Cache.php b/cache/Cache.php index 99c5885b1..b953523b9 100644 --- a/cache/Cache.php +++ b/cache/Cache.php @@ -76,7 +76,7 @@ *
- * SS_Cache::add_backend('two-level', 'TwoLevels', array(
+ * SS_Cache::add_backend('two-level', 'Two-Levels', array(
* 'slow_backend' => 'File',
* 'fast_backend' => 'Apc',
* 'slow_backend_options' => array(
diff --git a/composer.json b/composer.json
index 1ee62e382..8298e3391 100644
--- a/composer.json
+++ b/composer.json
@@ -26,5 +26,6 @@
},
"autoload": {
"classmap": ["tests/behat/features/bootstrap"]
- }
+ },
+ "minimum-stability": "dev"
}
diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php
index 35df43591..7954aa7c6 100644
--- a/control/HTTPRequest.php
+++ b/control/HTTPRequest.php
@@ -4,6 +4,9 @@
* Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
* (GET/POST/PUT/DELETE). This is used by {@link RequestHandler} objects to decide what to do.
*
+ * Caution: objects of this class are immutable, e.g. echo $request['a']; works as expected,
+ * but $request['a'] = '1'; has no effect.
+ *
* The intention is that a single SS_HTTPRequest object can be passed from one object to another, each object calling
* match() to get the information that they need out of the URL. This is generally handled by
* {@link RequestHandler::handleRequest()}.
diff --git a/css/UploadField.css b/css/UploadField.css
index a0ec3ed48..8d5ffcdd3 100644
--- a/css/UploadField.css
+++ b/css/UploadField.css
@@ -23,6 +23,13 @@ Used in side panels and action tabs
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status.ui-state-error-text { color: red; font-weight: bold; width: 150px; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status.ui-state-warning-text { color: #b7a403; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status.ui-state-success-text { color: #1f9433; }
+.ss-uploadfield .ss-uploadfield-item.ui-state-error .ss-uploadfield-item-preview { width: auto; height: auto; margin-right: 15px; }
+.ss-uploadfield .ss-uploadfield-item.ui-state-error .ss-uploadfield-item-info { margin-left: 0; }
+.ss-uploadfield .ss-uploadfield-item.ui-state-error .ss-uploadfield-item-info .ss-uploadfield-item-name { float: left; width: 70%; height: auto; }
+.ss-uploadfield .ss-uploadfield-item.ui-state-error .ss-uploadfield-item-info .ss-uploadfield-item-name .name { float: left; width: 100%; margin-bottom: 5px; }
+.ss-uploadfield .ss-uploadfield-item.ui-state-error .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status { float: left; width: 100%; padding: 0; text-align: left; }
+.ss-uploadfield .ss-uploadfield-item.ui-state-error .ss-uploadfield-item-info .ss-uploadfield-item-actions { float: right; width: 5%; min-height: 0; margin: 0; }
+.ss-uploadfield .ss-uploadfield-item.ui-state-error .ss-uploadfield-item-info .ss-uploadfield-item-actions .ss-uploadfield-item-cancel { position: relative; top: auto; }
.ss-uploadfield .ss-ui-button { display: block; float: left; margin: 0 10px 6px 0; }
.ss-uploadfield .ss-ui-button.ss-uploadfield-fromcomputer { position: relative; overflow: hidden; }
.ss-uploadfield .ss-uploadfield-files { margin: 0; padding: 0; overflow: auto; position: relative; }
diff --git a/dev/DebugView.php b/dev/DebugView.php
index 56efcfa3b..5d0d24a3a 100644
--- a/dev/DebugView.php
+++ b/dev/DebugView.php
@@ -144,8 +144,13 @@ class DebugView extends Object {
public function writeError($httpRequest, $errno, $errstr, $errfile, $errline, $errcontext) {
$errorType = isset(self::$error_types[$errno]) ? self::$error_types[$errno] : self::$unknown_error;
$httpRequestEnt = htmlentities($httpRequest, ENT_COMPAT, 'UTF-8');
+ if (ini_get('html_errors')) {
+ $errstr = strip_tags($errstr);
+ } else {
+ $errstr = Convert::raw2xml($errstr);
+ }
echo '';
- echo "[" . $errorType['title'] . '] ' . strip_tags($errstr) . "
";
+ echo "[" . $errorType['title'] . '] ' . $errstr . "
";
echo "$httpRequestEnt
";
echo "Line $errline in $errfile
";
echo '';
diff --git a/dev/install/install.php5 b/dev/install/install.php5
index dd4d125ba..a3ad99b85 100755
--- a/dev/install/install.php5
+++ b/dev/install/install.php5
@@ -48,7 +48,7 @@ $dirsToCheck = array(
if($dirsToCheck[0] == $dirsToCheck[1]) {
unset($dirsToCheck[1]);
}
-foreach($dirsToCheck as $dir) {
+foreach($dirsToCheck as $dir) {
//check this dir and every parent dir (until we hit the base of the drive)
// or until we hit a dir we can't read
do {
@@ -1061,12 +1061,16 @@ class InstallRequirements {
$helperPath = $adapters[$databaseClass]['helperPath'];
$class = str_replace('.php', '', basename($helperPath));
}
- return (class_exists($class)) ? new $class() : new MySQLDatabaseConfigurationHelper();
+ return (class_exists($class)) ? new $class() : false;
}
function requireDatabaseFunctions($databaseConfig, $testDetails) {
$this->testing($testDetails);
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
+ if (!$helper) {
+ $this->error("Couldn't load database helper code for ". $databaseConfig['type']);
+ return false;
+ }
$result = $helper->requireDatabaseFunctions($databaseConfig);
if($result) {
return true;
diff --git a/docs/en/reference/image.md b/docs/en/reference/image.md
index 7e9de7712..6583ec5cf 100644
--- a/docs/en/reference/image.md
+++ b/docs/en/reference/image.md
@@ -110,6 +110,16 @@ For output of an image tag with the image automatically resized to 80px width, y
For usage on a website form, see `[api:FileField]`.
If you want to upload images within the CMS, see `[api:UploadField]`.
+### Image Quality
+
+To adjust the quality of the generated images when they are resized add the following to your mysite/config/config.yml file:
+
+ :::yml
+ GDBackend:
+ default_quality: 90
+
+The default value is 75.
+
### Clearing Thumbnail Cache
Images are (like all other Files) synchronized with the SilverStripe database.
diff --git a/docs/en/reference/rssfeed.md b/docs/en/reference/rssfeed.md
index 515c18e01..b799b5c90 100644
--- a/docs/en/reference/rssfeed.md
+++ b/docs/en/reference/rssfeed.md
@@ -21,7 +21,7 @@ define an AbsoluteLink() method.
RSSFeed::linkToFeed($link, $title)
This line should go in your `[api:Controller]` subclass in the action you want
-to include the HTML link.
+to include the HTML link. Not all arguments are required, see `[api:RSSFeed]` and example below. Last Modified Time is expected in seconds like time().
:::php
$feed = new RSSFeed(
@@ -31,7 +31,9 @@ to include the HTML link.
$description,
$titleField,
$descriptionField,
- $authorField
+ $authorField,
+ $lastModifiedTime,
+ $etag
);
Creates a new `[api:RSSFeed]` instance to be returned. The arguments notify
diff --git a/docs/en/tutorials/2-extending-a-basic-site.md b/docs/en/tutorials/2-extending-a-basic-site.md
index 857dcab0e..491c21689 100644
--- a/docs/en/tutorials/2-extending-a-basic-site.md
+++ b/docs/en/tutorials/2-extending-a-basic-site.md
@@ -200,10 +200,12 @@ the date field will have the date format defined by your locale.
public function getCMSFields() {
$fields = parent::getCMSFields();
- $fields->addFieldToTab('Root.Main', $dateField = new DateField('Date','Article Date (for example: 20/12/2010)'), 'Content');
+ $dateField = new DateField('Date', 'Article Date (for example: 20/12/2010)');
$dateField->setConfig('showcalendar', true);
+ $dateField->setConfig('dateformat', 'dd/MM/YYYY');
+
$fields->addFieldToTab('Root.Main', $dateField, 'Content');
- $fields->addFieldToTab('Root.Main', new TextField('Author'), 'Content');
+ $fields->addFieldToTab('Root.Main', new TextField('Author', 'Author Name'), 'Content');
return $fields;
}
@@ -211,7 +213,7 @@ the date field will have the date format defined by your locale.
Let's walk through these changes.
:::php
- $fields->addFieldToTab('Root.Main', $dateField = new DateField('Date','Article Date (for example: 20/12/2010)'), 'Content');
+ $dateField = new DateField('Date', 'Article Date (for example: 20/12/2010)');
*$dateField* is declared in order to change the configuration of the DateField.
@@ -226,7 +228,7 @@ By enabling *showCalendar* you show a calendar overlay when clicking on the fiel
*dateFormat* allows you to specify how you wish the date to be entered and displayed in the CMS field. See the `[api:DateField]` documentation for more configuration options.
:::php
- $fields->addFieldToTab('Root.Main', new TextField('Author','Author Name'), 'Content');
+ $fields->addFieldToTab('Root.Main', new TextField('Author', 'Author Name'), 'Content');
By default the field name *'Date'* or *'Author'* is shown as the title, however this might not be that helpful so to change the title, add the new title as the second argument.
@@ -335,19 +337,23 @@ Now let's make a purely cosmetic change that nevertheless helps to make the info
Add the following field to the *ArticleHolder* and *ArticlePage* classes:
:::php
- private static $icon = "framework/docs/en/tutorials/_images/treeicons/news-file.gif";
+ private static $icon = "cms/images/treeicons/news-file.gif";
And this one to the *HomePage* class:
:::php
- private static $icon = "framework/docs/en/tutorials/_images/treeicons/home-file.gif";
+ private static $icon = "cms/images/treeicons/home-file.png";
-This will change the icons for the pages in the CMS.
+This will change the icons for the pages in the CMS.
![](_images/tutorial2_icons2.jpg)
+
+Note: The `news-file` icon may not exist in a default SilverStripe installation. Try adding your own image or choosing a different one from the `treeicons` collection.
+
+
## Showing the latest news on the homepage
It would be nice to greet page visitors with a summary of the latest news when they visit the homepage. This requires a little more code though - the news articles are not direct children of the homepage, so we can't use the *Children* control. We can get the data for news articles by implementing our own function in *HomePage_Controller*.
diff --git a/filesystem/Folder.php b/filesystem/Folder.php
index 3af480f51..cbef4301d 100644
--- a/filesystem/Folder.php
+++ b/filesystem/Folder.php
@@ -40,11 +40,13 @@ class Folder extends File {
/**
* Find the given folder or create it both as {@link Folder} database records
- * and on the filesystem. If necessary, creates parent folders as well.
+ * and on the filesystem. If necessary, creates parent folders as well. If it's
+ * unable to find or make the folder, it will return null (as /assets is unable
+ * to be represented by a Folder DataObject)
*
* @param $folderPath string Absolute or relative path to the file.
* If path is relative, its interpreted relative to the "assets/" directory.
- * @return Folder
+ * @return Folder|null
*/
public static function find_or_make($folderPath) {
// Create assets directory, if it is missing
diff --git a/filesystem/Upload.php b/filesystem/Upload.php
index a6ab0beba..ec8b7ca12 100644
--- a/filesystem/Upload.php
+++ b/filesystem/Upload.php
@@ -134,7 +134,9 @@ class Upload extends Controller {
$file = $nameFilter->filter($tmpFile['name']);
$fileName = basename($file);
- $relativeFilePath = $parentFolder ? $parentFolder->getRelativePath() . "$fileName" : $fileName;
+ $relativeFilePath = $parentFolder
+ ? $parentFolder->getRelativePath() . "$fileName"
+ : ASSETS_DIR . "/" . $fileName;
// Create a new file record (or try to retrieve an existing one)
if(!$this->file) {
diff --git a/forms/Form.php b/forms/Form.php
index 147219443..297fe02ca 100644
--- a/forms/Form.php
+++ b/forms/Form.php
@@ -1473,7 +1473,7 @@ class Form extends RequestHandler {
. " value=\"" . $this->FormAction() . "\" />\n";
$content .= "FormName() . "\" />\n";
$content .= "FormMethod() . "\" />\n";
- $content .= "FormEncType() . "\" />\n";
+ $content .= "getEncType() . "\" />\n";
return $content;
}
diff --git a/forms/OptionsetField.php b/forms/OptionsetField.php
index 5e2e36e12..010c9ff22 100644
--- a/forms/OptionsetField.php
+++ b/forms/OptionsetField.php
@@ -93,7 +93,7 @@ class OptionsetField extends DropdownField {
public function performReadonlyTransformation() {
// Source and values are DataObject sets.
$field = $this->castedCopy('LookupField');
- $field->setValue($this->getSource());
+ $field->setSource($this->getSource());
$field->setReadonly(true);
return $field;
diff --git a/forms/TreeDropdownField.php b/forms/TreeDropdownField.php
index 2c8feb690..34bf57f22 100644
--- a/forms/TreeDropdownField.php
+++ b/forms/TreeDropdownField.php
@@ -279,7 +279,7 @@ class TreeDropdownField extends FormField {
if( isset($_REQUEST['forceValue']) || $this->value ) {
$forceValue = ( isset($_REQUEST['forceValue']) ? $_REQUEST['forceValue'] : $this->value);
if(($values = preg_split('/,\s*/', $forceValue)) && count($values)) foreach($values as $value) {
- if(!$value) continue;
+ if(!$value || $value == 'unchanged') continue;
$obj->markToExpose($this->objectForKey($value));
}
diff --git a/forms/UploadField.php b/forms/UploadField.php
index 6bb77a186..73bf334c5 100644
--- a/forms/UploadField.php
+++ b/forms/UploadField.php
@@ -1259,7 +1259,7 @@ class UploadField extends FileField {
// Format response with json
$response = new SS_HTTPResponse(Convert::raw2json(array($return)));
$response->addHeader('Content-Type', 'text/plain');
- if(!empty($return['error'])) $response->setStatusCode(403);
+ if (!empty($return['error'])) $response->setStatusCode(403);
return $response;
}
@@ -1300,7 +1300,9 @@ class UploadField extends FileField {
// Resolve expected folder name
$folderName = $this->getFolderName();
$folder = Folder::find_or_make($folderName);
- $parentPath = BASE_PATH."/".$folder->getFilename();
+ $parentPath = $folder
+ ? BASE_PATH."/".$folder->getFilename()
+ : ASSETS_PATH."/";
// check if either file exists
$exists = false;
diff --git a/forms/gridfield/GridFieldSortableHeader.php b/forms/gridfield/GridFieldSortableHeader.php
index 8c265416f..70dcb82d6 100644
--- a/forms/gridfield/GridFieldSortableHeader.php
+++ b/forms/gridfield/GridFieldSortableHeader.php
@@ -92,6 +92,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
foreach($columns as $columnField) {
$currentColumn++;
$metadata = $gridField->getColumnMetadata($columnField);
+ $fieldName = str_replace('.', '-', $columnField);
$title = $metadata['title'];
if(isset($this->fieldSorting[$columnField]) && $this->fieldSorting[$columnField]) {
@@ -132,7 +133,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
}
$field = Object::create(
- 'GridField_FormAction', $gridField, 'SetOrder'.$columnField, $title,
+ 'GridField_FormAction', $gridField, 'SetOrder'.$fieldName, $title,
"sort$dir", array('SortColumn' => $columnField)
)->addExtraClass('ss-gridfield-sort');
@@ -148,10 +149,10 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
if($currentColumn == count($columns)
&& $gridField->getConfig()->getComponentByType('GridFieldFilterHeader')){
- $field = new LiteralField($columnField,
+ $field = new LiteralField($fieldName,
'');
} else {
- $field = new LiteralField($columnField, '' . $title . '');
+ $field = new LiteralField($fieldName, '' . $title . '');
}
}
$forTemplate->Fields->push($field);
diff --git a/javascript/UploadField_downloadtemplate.js b/javascript/UploadField_downloadtemplate.js
index 02500b389..43340b201 100644
--- a/javascript/UploadField_downloadtemplate.js
+++ b/javascript/UploadField_downloadtemplate.js
@@ -1,9 +1,11 @@
window.tmpl.cache['ss-uploadfield-downloadtemplate'] = tmpl(
'{% for (var i=0, files=o.files, l=files.length, file=files[0]; i' +
- '' +
- '' +
- '' +
+ '{% if (file.thumbnail_url) { %}' +
+ '' +
+ '' +
+ '' +
+ '{% } %}' +
'' +
'{% if (!file.error) { %}' +
'' +
diff --git a/model/DataObject.php b/model/DataObject.php
index 54a00595e..c9fbb63ac 100644
--- a/model/DataObject.php
+++ b/model/DataObject.php
@@ -257,7 +257,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$db = DB::getConn();
if($db->hasField($class, 'ClassName')) {
$existing = $db->query("SELECT DISTINCT \"ClassName\" FROM \"$class\"")->column();
- $classNames = array_unique(array_merge($existing, $classNames));
+ $classNames = array_unique(array_merge($classNames, $existing));
}
self::$classname_spec_cache[$class] = "Enum('" . implode(', ', $classNames) . "')";
@@ -2186,6 +2186,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
$dataQuery = new DataQuery($tableClass);
+
+ // Reset query parameter context to that of this DataObject
+ if($params = $this->getSourceQueryParams()) {
+ foreach($params as $key => $value) $dataQuery->setQueryParam($key, $value);
+ }
// TableField sets the record ID to "new" on new row data, so don't try doing anything in that case
if(!is_numeric($this->record['ID'])) return false;
diff --git a/model/GroupedList.php b/model/GroupedList.php
index 9e9a693bb..50fe7723a 100644
--- a/model/GroupedList.php
+++ b/model/GroupedList.php
@@ -43,6 +43,7 @@ class GroupedList extends SS_ListDecorator {
$result = new ArrayList();
foreach ($grouped as $indVal => $list) {
+ $list = GroupedList::create($list);
$result->push(new ArrayData(array(
$index => $indVal,
$children => $list
diff --git a/model/Versioned.php b/model/Versioned.php
index 37868633a..5c1911da5 100644
--- a/model/Versioned.php
+++ b/model/Versioned.php
@@ -182,13 +182,13 @@ class Versioned extends DataExtension {
* @todo Should this all go into VersionedDataQuery?
*/
public function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) {
- $baseTable = ClassInfo::baseDataClass($dataQuery->dataClass());
-
- switch($dataQuery->getQueryParam('Versioned.mode')) {
- // Noop
- case '':
- break;
+ if(!$dataQuery || !$dataQuery->getQueryParam('Versioned.mode')) {
+ return;
+ }
+ $baseTable = ClassInfo::baseDataClass($dataQuery->dataClass());
+
+ switch($dataQuery->getQueryParam('Versioned.mode')) {
// Reading a specific data from the archive
case 'archive':
$date = $dataQuery->getQueryParam('Versioned.date');
@@ -1203,6 +1203,7 @@ class Versioned extends DataExtension {
$oldMode = Versioned::get_reading_mode();
Versioned::reading_stage($stage);
+ $this->owner->forceChange();
$result = $this->owner->write(false, $forceInsert);
Versioned::set_reading_mode($oldMode);
diff --git a/model/fieldtypes/Enum.php b/model/fieldtypes/Enum.php
index 40dd4d7f2..9f05bdd03 100644
--- a/model/fieldtypes/Enum.php
+++ b/model/fieldtypes/Enum.php
@@ -38,7 +38,7 @@ class Enum extends StringField {
public function __construct($name = null, $enum = NULL, $default = NULL) {
if($enum) {
if(!is_array($enum)) {
- $enum = preg_split("/ *, */", trim(trim($enum, ',')));
+ $enum = preg_split("/ *, */", trim($enum));
}
$this->enum = $enum;
diff --git a/model/fieldtypes/MultiEnum.php b/model/fieldtypes/MultiEnum.php
index 0ddb4bf57..7e7cf17b4 100644
--- a/model/fieldtypes/MultiEnum.php
+++ b/model/fieldtypes/MultiEnum.php
@@ -18,7 +18,7 @@ class MultiEnum extends Enum {
// Validate and assign the default
$this->default = null;
if($default) {
- $defaults = preg_split('/ *, */',trim(trim($default, ',')));
+ $defaults = preg_split('/ *, */',trim($default));
foreach($defaults as $thisDefault) {
if(!in_array($thisDefault, $this->enum)) {
user_error("Enum::__construct() The default value '$thisDefault' does not match "
diff --git a/scss/UploadField.scss b/scss/UploadField.scss
index a4aa878f2..61a4bccbb 100644
--- a/scss/UploadField.scss
+++ b/scss/UploadField.scss
@@ -85,6 +85,52 @@
}
}
}
+
+ //Upload/Validation error
+ &.ui-state-error
+ {
+ .ss-uploadfield-item-preview {
+ width: auto;
+ height: auto;
+ margin-right: 15px;
+ }
+
+ .ss-uploadfield-item-info {
+ margin-left: 0;
+
+ .ss-uploadfield-item-name {
+ float: left;
+ width: 70%;
+ height: auto;
+
+ .name
+ {
+ float: left;
+ width: 100%;
+ margin-bottom: 5px;
+ }
+
+ .ss-uploadfield-item-status {
+ float: left;
+ width: 100%;
+ padding: 0;
+ text-align: left;
+ }
+ }
+
+ .ss-uploadfield-item-actions {
+ float: right;
+ width: 5%;
+ min-height: 0;
+ margin: 0;
+
+ .ss-uploadfield-item-cancel {
+ position: relative;
+ top: auto;
+ }
+ }
+ }
+ }
}
.ss-ui-button {
display: block;
diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php
index 41fc7e0d0..c7418590a 100644
--- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php
+++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php
@@ -114,11 +114,20 @@ class CmsUiContext extends BehatContext {
$table_element = null;
foreach ($table_elements as $table) {
$table_title_element = $table->find('css', '.title');
- if ($table_title_element->getText() === $title) {
+ if ($table_title_element && $table_title_element->getText() === $title) {
$table_element = $table;
break;
}
}
+
+ // Some {@link GridField} tables don't have a visible title, so look for a fieldset with data-name instead
+ if(!$table_element) {
+ $fieldset = $page->findAll('xpath', "//fieldset[@data-name='$title']");
+ if(is_array($fieldset) && isset($fieldset[0])) {
+ $table_element = $fieldset[0]->find('css', '.ss-gridfield-table');
+ }
+ }
+
assertNotNull($table_element, sprintf('Table `%s` not found', $title));
return $table_element;
diff --git a/tests/forms/OptionsetFieldTest.php b/tests/forms/OptionsetFieldTest.php
index 0bb4070d1..81d66fe77 100644
--- a/tests/forms/OptionsetFieldTest.php
+++ b/tests/forms/OptionsetFieldTest.php
@@ -24,4 +24,14 @@ class OptionsetFieldTest extends SapphireTest {
''
);
}
+
+ public function testReadonlyField() {
+ $sourceArray = array(0 => 'No', 1 => 'Yes');
+ $field = new OptionsetField('FeelingOk', 'are you feeling ok?', $sourceArray, 1);
+ $field->setEmptyString('(Select one)');
+ $field->setValue(1);
+ $readonlyField = $field->performReadonlyTransformation();
+ preg_match('/Yes/', $field->Field(), $matches);
+ $this->assertEquals($matches[0], 'Yes');
+ }
}
diff --git a/tests/forms/uploadfield/UploadFieldTest.php b/tests/forms/uploadfield/UploadFieldTest.php
index bdad6147e..0510ac059 100644
--- a/tests/forms/uploadfield/UploadFieldTest.php
+++ b/tests/forms/uploadfield/UploadFieldTest.php
@@ -677,6 +677,45 @@ class UploadFieldTest extends FunctionalTest {
$this->assertNotContains($fileSubfolder->ID, $itemIDs, 'Does not contain file in subfolder');
}
+ /**
+ * Tests that UploadField::fileexist works
+ */
+ public function testFileExists() {
+ $this->loginWithPermission('ADMIN');
+
+ // Check that fileexist works on subfolders
+ $nonFile = uniqid().'.txt';
+ $responseEmpty = $this->mockFileExists('NoRelationField', $nonFile);
+ $responseEmptyData = json_decode($responseEmpty->getBody());
+ $this->assertFalse($responseEmpty->isError());
+ $this->assertFalse($responseEmptyData->exists);
+
+ // Check that filexists works on root folder
+ $responseRoot = $this->mockFileExists('RootFolderTest', $nonFile);
+ $responseRootData = json_decode($responseRoot->getBody());
+ $this->assertFalse($responseRoot->isError());
+ $this->assertFalse($responseRootData->exists);
+
+ // Check that uploaded files can be detected in the root
+ $tmpFileName = 'testUploadBasic.txt';
+ $response = $this->mockFileUpload('RootFolderTest', $tmpFileName);
+ $this->assertFalse($response->isError());
+ $this->assertFileExists(ASSETS_PATH . "/$tmpFileName");
+ $responseExists = $this->mockFileExists('RootFolderTest', $tmpFileName);
+ $responseExistsData = json_decode($responseExists->getBody());
+ $this->assertFalse($responseExists->isError());
+ $this->assertTrue($responseExistsData->exists);
+
+ // Check that uploaded files can be detected
+ $response = $this->mockFileUpload('NoRelationField', $tmpFileName);
+ $this->assertFalse($response->isError());
+ $this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/$tmpFileName");
+ $responseExists = $this->mockFileExists('NoRelationField', $tmpFileName);
+ $responseExistsData = json_decode($responseExists->getBody());
+ $this->assertFalse($responseExists->isError());
+ $this->assertTrue($responseExistsData->exists);
+ }
+
protected function getMockForm() {
return new Form(new Controller(), 'Form', new FieldList(), new FieldList());
}
@@ -751,6 +790,12 @@ class UploadFieldTest extends FunctionalTest {
);
}
+ protected function mockFileExists($fileField, $fileName) {
+ return $this->get(
+ "UploadFieldTest_Controller/Form/field/{$fileField}/fileexists?filename=".urlencode($fileName)
+ );
+ }
+
/**
* Simulates a physical file deletion
*
@@ -807,7 +852,14 @@ class UploadFieldTest extends FunctionalTest {
}
// Remove left over folders and any files that may exist
- if(file_exists('../assets/UploadFieldTest')) Filesystem::removeFolder('../assets/UploadFieldTest');
+ if(file_exists(ASSETS_PATH.'/UploadFieldTest')) {
+ Filesystem::removeFolder(ASSETS_PATH.'/UploadFieldTest');
+ }
+
+ // Remove file uploaded to root folder
+ if(file_exists(ASSETS_PATH.'/testUploadBasic.txt')) {
+ unlink(ASSETS_PATH.'/testUploadBasic.txt');
+ }
}
}
@@ -893,6 +945,9 @@ class UploadFieldTestForm extends Form implements TestOnly {
if(empty($controller)) {
$controller = new UploadFieldTest_Controller();
}
+
+ $fieldRootFolder = UploadField::create('RootFolderTest')
+ ->setFolderName('/');
$fieldNoRelation = UploadField::create('NoRelationField')
->setFolderName('UploadFieldTest');
@@ -949,6 +1004,7 @@ class UploadFieldTestForm extends Form implements TestOnly {
$fieldAllowedExtensions->getValidator()->setAllowedExtensions(array('txt'));
$fields = new FieldList(
+ $fieldRootFolder,
$fieldNoRelation,
$fieldHasOne,
$fieldHasOneMaxOne,
diff --git a/tests/model/DataObjectSchemaGenerationTest.php b/tests/model/DataObjectSchemaGenerationTest.php
index 6c2d86c76..c33cb7e36 100644
--- a/tests/model/DataObjectSchemaGenerationTest.php
+++ b/tests/model/DataObjectSchemaGenerationTest.php
@@ -124,6 +124,57 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
// Restore old indexes
Config::unnest();
}
+
+ /**
+ * Tests the generation of the ClassName spec and ensure it's not unnecessarily influenced
+ * by the order of classnames of existing records
+ */
+ public function testClassNameSpecGeneration() {
+
+ // Test with blank entries
+ DataObject::clear_classname_spec_cache();
+ $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
+ $this->assertEquals(
+ "Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
+ $fields['ClassName']
+ );
+
+ // Test with instance of subclass
+ $item1 = new DataObjectSchemaGenerationTest_IndexDO();
+ $item1->write();
+ DataObject::clear_classname_spec_cache();
+ $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
+ $this->assertEquals(
+ "Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
+ $fields['ClassName']
+ );
+ $item1->delete();
+
+ // Test with instance of main class
+ $item2 = new DataObjectSchemaGenerationTest_DO();
+ $item2->write();
+ DataObject::clear_classname_spec_cache();
+ $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
+ $this->assertEquals(
+ "Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
+ $fields['ClassName']
+ );
+ $item2->delete();
+
+ // Test with instances of both classes
+ $item1 = new DataObjectSchemaGenerationTest_IndexDO();
+ $item1->write();
+ $item2 = new DataObjectSchemaGenerationTest_DO();
+ $item2->write();
+ DataObject::clear_classname_spec_cache();
+ $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
+ $this->assertEquals(
+ "Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
+ $fields['ClassName']
+ );
+ $item1->delete();
+ $item2->delete();
+ }
}
class DataObjectSchemaGenerationTest_DO extends DataObject implements TestOnly {
diff --git a/tests/model/GroupedListTest.php b/tests/model/GroupedListTest.php
index 7bdad0949..1c83d90b8 100644
--- a/tests/model/GroupedListTest.php
+++ b/tests/model/GroupedListTest.php
@@ -48,5 +48,79 @@ class GroupedListTest extends SapphireTest {
$this->assertEquals(2, count($last->Children));
$this->assertEquals('CCC', $last->Name);
}
+
+ public function testGroupedByChildren(){
+ $list = GroupedList::create(
+ ArrayList::create(
+ array(
+ ArrayData::create(array(
+ 'Name' => 'AAA',
+ 'Number' => '111',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'BBB',
+ 'Number' => '111',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'AAA',
+ 'Number' => '222',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'BBB',
+ 'Number' => '111',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'AAA',
+ 'Number' => '111',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'AAA',
+ 'Number' => '333',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'BBB',
+ 'Number' => '222',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'BBB',
+ 'Number' => '333',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'AAA',
+ 'Number' => '111',
+ )),
+ ArrayData::create(array(
+ 'Name' => 'AAA',
+ 'Number' => '333',
+ ))
+ )
+ )
+ );
+ $grouped = $list->GroupedBy('Name');
+
+ foreach($grouped as $group){
+ $children = $group->Children;
+ $childGroups = $children->GroupedBy('Number');
+
+ $this->assertEquals(3, count($childGroups));
+
+ $first = $childGroups->first();
+ $last = $childGroups->last();
+
+ if($group->Name == 'AAA'){
+ $this->assertEquals(3, count($first->Children));
+ $this->assertEquals('111', $first->Number);
+ $this->assertEquals(2, count($last->Children));
+ $this->assertEquals('333', $last->Number);
+ }
+
+ if($group->Name == 'BBB'){
+ $this->assertEquals(2, count($first->Children));
+ $this->assertEquals('111', $first->Number);
+ $this->assertEquals(1, count($last->Children));
+ $this->assertEquals('333', $last->Number);
+ }
+ }
+ }
}
diff --git a/tests/model/VersionedTest.php b/tests/model/VersionedTest.php
index 3523c6add..7b64a56c5 100644
--- a/tests/model/VersionedTest.php
+++ b/tests/model/VersionedTest.php
@@ -549,6 +549,47 @@ class VersionedTest extends SapphireTest {
'Writes to and reads from default stage even if a non-matching stage is set'
);
}
+
+ /**
+ * Test that publishing processes respects lazy loaded fields
+ */
+ public function testLazyLoadFields() {
+ $originalMode = Versioned::get_reading_mode();
+
+ // Generate staging record and retrieve it from stage in live mode
+ Versioned::reading_stage('Stage');
+ $obj = new VersionedTest_Subclass();
+ $obj->Name = 'bob';
+ $obj->ExtraField = 'Field Value';
+ $obj->write();
+ $objID = $obj->ID;
+ $filter = sprintf('"VersionedTest_DataObject"."ID" = \'%d\'', Convert::raw2sql($objID));
+ Versioned::reading_stage('Live');
+
+ // Check fields are unloaded prior to access
+ $objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false);
+ $lazyFields = $objLazy->getQueriedDatabaseFields();
+ $this->assertTrue(isset($lazyFields['ExtraField_Lazy']));
+ $this->assertEquals('VersionedTest_Subclass', $lazyFields['ExtraField_Lazy']);
+
+ // Check lazy loading works when viewing a Stage object in Live mode
+ $this->assertEquals('Field Value', $objLazy->ExtraField);
+
+ // Test that writeToStage respects lazy loaded fields
+ $objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false);
+ $objLazy->writeToStage('Live');
+ $objLive = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Live', $filter, false);
+ $liveLazyFields = $objLive->getQueriedDatabaseFields();
+
+ // Check fields are unloaded prior to access
+ $this->assertTrue(isset($liveLazyFields['ExtraField_Lazy']));
+ $this->assertEquals('VersionedTest_Subclass', $liveLazyFields['ExtraField_Lazy']);
+
+ // Check that live record has original value
+ $this->assertEquals('Field Value', $objLive->ExtraField);
+
+ Versioned::set_reading_mode($originalMode);
+ }
}