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 @@ *

Using APC as a store

* * - * 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); + } }