Merge remote-tracking branch 'origin/3.1'

Conflicts:
	.travis.yml
This commit is contained in:
Simon Welsh 2014-03-16 09:36:48 +13:00
commit 8f31352039
30 changed files with 382 additions and 39 deletions

View File

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

2
cache/Cache.php vendored
View File

@ -76,7 +76,7 @@
* <h2>Using APC as a store</h2>
*
* <code>
* 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(

View File

@ -26,5 +26,6 @@
},
"autoload": {
"classmap": ["tests/behat/features/bootstrap"]
}
},
"minimum-stability": "dev"
}

View File

@ -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()}.

View File

@ -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; }

View File

@ -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 '<div class="info ' . $errorType['class'] . '">';
echo "<h1>[" . $errorType['title'] . '] ' . strip_tags($errstr) . "</h1>";
echo "<h1>[" . $errorType['title'] . '] ' . $errstr . "</h1>";
echo "<h3>$httpRequestEnt</h3>";
echo "<p>Line <strong>$errline</strong> in <strong>$errfile</strong></p>";
echo '</div>';

View File

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

View File

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

View File

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

View File

@ -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)
<div class="hint" markdown="1">
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.
</div>
## 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*.

View File

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

View File

@ -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) {

View File

@ -1473,7 +1473,7 @@ class Form extends RequestHandler {
. " value=\"" . $this->FormAction() . "\" />\n";
$content .= "<input type=\"hidden\" name=\"_form_name\" value=\"" . $this->FormName() . "\" />\n";
$content .= "<input type=\"hidden\" name=\"_form_method\" value=\"" . $this->FormMethod() . "\" />\n";
$content .= "<input type=\"hidden\" name=\"_form_enctype\" value=\"" . $this->FormEncType() . "\" />\n";
$content .= "<input type=\"hidden\" name=\"_form_enctype\" value=\"" . $this->getEncType() . "\" />\n";
return $content;
}

View File

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

View File

@ -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));
}

View File

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

View File

@ -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,
'<button name="showFilter" class="ss-gridfield-button-filter trigger"></button>');
} else {
$field = new LiteralField($columnField, '<span class="non-sortable">' . $title . '</span>');
$field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
}
}
$forTemplate->Fields->push($field);

View File

@ -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<l; file=files[++i]) { %}' +
'<li class="ss-uploadfield-item template-download{% if (file.error) { %} ui-state-error{% } %}" data-fileid="{%=file.id%}">' +
'<div class="ss-uploadfield-item-preview preview"><span>' +
'<img src="{%=file.thumbnail_url%}" alt="" />' +
'</span></div>' +
'{% if (file.thumbnail_url) { %}' +
'<div class="ss-uploadfield-item-preview preview"><span>' +
'<img src="{%=file.thumbnail_url%}" alt="" />' +
'</span></div>' +
'{% } %}' +
'<div class="ss-uploadfield-item-info">' +
'{% if (!file.error) { %}' +
'<input type="hidden" name="{%=file.fieldname%}[Files][]" value="{%=file.id%}" />' +

View File

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

View File

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

View File

@ -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);

View File

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

View File

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

View File

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

View File

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

View File

@ -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');
}
}

View File

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

View File

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

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}