diff --git a/admin/javascript/LeftAndMain.Tree.js b/admin/javascript/LeftAndMain.Tree.js
index 1e2ea02ac..bfa8b5b3d 100644
--- a/admin/javascript/LeftAndMain.Tree.js
+++ b/admin/javascript/LeftAndMain.Tree.js
@@ -285,7 +285,8 @@
}
// Replace inner content
- node.addClass(origClasses).html(newNode.html());
+ var origChildren = node.children('ul').detach();
+ node.addClass(origClasses).html(newNode.html()).append(origChildren);
if (nextNode && nextNode.length) {
this.jstree('move_node', node, nextNode, 'before');
@@ -354,23 +355,25 @@
return;
}
+ var correctStateFn = function(node) {
+ self.jstree('deselect_all');
+ self.jstree('select_node', node);
+ // Similar to jstree's correct_state, but doesn't remove children
+ var hasChildren = (node.children('ul').length > 0);
+ node.toggleClass('jstree-leaf', !hasChildren);
+ if(!hasChildren) node.removeClass('jstree-closed jstree-open');
+ };
+
// Check if node exists, create if necessary
if(node.length) {
self.updateNode(node, nodeData.html, nodeData);
setTimeout(function() {
- self.jstree('deselect_all');
- self.jstree('select_node', node);
- // Manually correct state, which checks for children and
- // removes toggle arrow (should really be done by jstree internally)
- self.jstree('correct_state', node);
+ correctStateFn(node) ;
}, 500);
} else {
includesNewNode = true;
self.createNode(nodeData.html, nodeData, function(newNode) {
- self.jstree('deselect_all');
- self.jstree('select_node', newNode);
- // Manually remove toggle node, see above
- self.jstree('correct_state', newNode);
+ correctStateFn(newNode);
});
}
});
diff --git a/dev/install/config-form.html b/dev/install/config-form.html
index d4cfc2fc1..9cfecc056 100644
--- a/dev/install/config-form.html
+++ b/dev/install/config-form.html
@@ -1,4 +1,4 @@
-
+
@@ -244,8 +244,8 @@
Theme selection Step 4 of 5
You can change the theme or download another from the SilverStripe website after installation.
Confirm Install Step 5 of 5
diff --git a/docs/en/topics/datamodel.md b/docs/en/topics/datamodel.md
index bccc2de7a..ac415d84b 100755
--- a/docs/en/topics/datamodel.md
+++ b/docs/en/topics/datamodel.md
@@ -246,44 +246,6 @@ use case could be when you want to find all the members that does not exist in a
// ... Finding all members that does not belong to $group.
$otherMembers = Member::get()->subtract($group->Members());
-
-### Relation filters
-
-So far we have only filtered a data list by fields on the object that you're requesting. For simple cases, this might
-be okay, but often, a data model is made up of a number of related objects. For example, in SilverStripe each member
-can be placed in a number of groups, and each group has a number of permissions.
-
-For this, the SilverStripe ORM supports **Relation Filters**. Any ORM request can be filtered by fields on a related
-object by specifying the filter key as `.`. You can chain relations together
-as many times as is necessary.
-
-For example, this will return all members assigned to a group that has a permission record with the code "ADMIN". In
-other words, it will return all administrators.
-
- :::php
- $members = Member::get()->filter(array(
- 'Groups.Permissions.Code:ExactMatch' => 'ADMIN',
- ));
-
-Note that we are just joining these tables to filter the records. Even if a member is in more than 1 administrator
-group, unique members will still be returned by this query.
-
-The other features of filters can be applied to relation filters as well. This will return all members in groups whose
-names start with 'A' or 'B'.
-
- :::php
- $members = Member::get()->filter(array(
- 'Groups.Title:StartsWith' => array('A', 'B'),
- ));
-
-You can even follow a relation back to the original model class! This will return all members are in at least 1 group
-that also has a member called Sam.
-
- :::php
- $members = Member::get()->filter(array(
- 'Groups.Members.FirstName' => 'Sam'
- ));
-
### Raw SQL options for advanced users
Occasionally, the system described above won't let you do exactly what you need to do. In these situtations, we have
diff --git a/forms/gridfield/GridFieldDeleteAction.php b/forms/gridfield/GridFieldDeleteAction.php
index 8229a8a8a..a1a489aa9 100644
--- a/forms/gridfield/GridFieldDeleteAction.php
+++ b/forms/gridfield/GridFieldDeleteAction.php
@@ -133,7 +133,11 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
if($actionName == 'deleterecord' && !$item->canDelete()) {
throw new ValidationException(_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"),0);
}
- $gridField->getList()->remove($item);
+ if($actionName == 'deleterecord') {
+ $item->delete();
+ } else {
+ $gridField->getList()->remove($item);
+ }
}
}
}
diff --git a/model/DataQuery.php b/model/DataQuery.php
index 2445df599..625dbe502 100644
--- a/model/DataQuery.php
+++ b/model/DataQuery.php
@@ -656,6 +656,7 @@ function max($field) {
$fieldExpression = $this->expressionForField($field, $subSelect);
$subSelect->setSelect(array());
$subSelect->selectField($fieldExpression, $field);
+ $subSelect->setOrderBy(null);
$this->where($this->expressionForField($field, $this).' NOT IN ('.$subSelect->sql().')');
return $this;
diff --git a/model/Hierarchy.php b/model/Hierarchy.php
index d6100fa29..7aefd0868 100644
--- a/model/Hierarchy.php
+++ b/model/Hierarchy.php
@@ -575,22 +575,9 @@ class Hierarchy extends DataExtension {
if(!$showAll) $children = $children->where('"ShowInMenus" = 1');
// Query the live site
- $children->dataQuery()->setQueryParam('Versioned.mode', 'stage');
+ $children->dataQuery()->setQueryParam('Versioned.mode', $onlyDeletedFromStage ? 'stage_unique' : 'stage');
$children->dataQuery()->setQueryParam('Versioned.stage', 'Live');
-
- if($onlyDeletedFromStage) {
- // Note that this makes a second query, and could be optimised to be a join
- $stageChildren = DataObject::get($baseClass)
- ->where("\"{$baseClass}\".\"ID\" != $id");
- $stageChildren->dataQuery()->setQueryParam('Versioned.mode', 'stage');
- $stageChildren->dataQuery()->setQueryParam('Versioned.stage', '');
-
- $ids = $stageChildren->column("ID");
- if($ids) {
- $children = $children->where("\"$baseClass\".\"ID\" NOT IN (" . implode(',',$ids) . ")");
- }
- }
-
+
return $children;
}
diff --git a/model/Versioned.php b/model/Versioned.php
index edb5f1363..027ea7e26 100644
--- a/model/Versioned.php
+++ b/model/Versioned.php
@@ -191,8 +191,28 @@ class Versioned extends DataExtension {
}
}
break;
-
-
+
+ // Reading a specific stage, but only return items that aren't in any other stage
+ case 'stage_unique':
+ $stage = $dataQuery->getQueryParam('Versioned.stage');
+
+ // Recurse to do the default stage behavior (must be first, we rely on stage renaming happening before below)
+ $dataQuery->setQueryParam('Versioned.mode', 'stage');
+ $this->augmentSQL($query, $dataQuery);
+
+ // Now exclude any ID from any other stage. Note that we double rename to avoid the regular stage rename
+ // renaming all subquery references to be Versioned.stage
+ foreach($this->stages as $excluding) {
+ if ($excluding == $stage) continue;
+
+ $tempName = 'ExclusionarySource_'.$excluding;
+ $excludingTable = $baseTable . ($excluding && $excluding != $this->defaultStage ? "_$excluding" : '');
+
+ $query->addWhere('"'.$baseTable.'"."ID" NOT IN (SELECT ID FROM "'.$tempName.'")');
+ $query->renameTable($tempName, $excludingTable);
+ }
+ break;
+
// Return all version instances
case 'all_versions':
case 'latest_versions':
@@ -208,6 +228,7 @@ class Versioned extends DataExtension {
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, $name), $name);
}
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, 'RecordID'), "ID");
+ $query->addOrderBy(sprintf('"%s_versions"."%s"', $baseTable, 'Version'));
// latest_version has one more step
// Return latest version instances, regardless of whether they are on a particular stage
@@ -363,7 +384,7 @@ class Versioned extends DataExtension {
$versionIndexes = array_merge(
array(
- 'RecordID_Version' => array('type' => 'unique', 'value' => 'RecordID,Version'),
+ 'RecordID_Version' => array('type' => 'unique', 'value' => '"RecordID","Version"'),
'RecordID' => true,
'Version' => true,
),
diff --git a/search/filters/GreaterThanFilter.php b/search/filters/GreaterThanFilter.php
index 5d9b753cc..8756d908c 100644
--- a/search/filters/GreaterThanFilter.php
+++ b/search/filters/GreaterThanFilter.php
@@ -14,11 +14,12 @@ class GreaterThanFilter extends SearchFilter {
*/
public function apply(DataQuery $query) {
$this->model = $query->applyRelation($this->relation);
- return $query->where(sprintf(
- "%s > '%s'",
- $this->getDbName(),
- Convert::raw2sql($this->getDbFormattedValue())
- ));
+ $value = $this->getDbFormattedValue();
+
+ if(is_numeric($value)) $filter = sprintf("%s > %s", $this->getDbName(), Convert::raw2sql($value));
+ else $filter = sprintf("%s > '%s'", $this->getDbName(), Convert::raw2sql($value));
+
+ return $query->where($filter);
}
public function isEmpty() {
diff --git a/tests/forms/RequirementsTest.php b/tests/forms/RequirementsTest.php
index ac588a0ba..0958fd8bc 100644
--- a/tests/forms/RequirementsTest.php
+++ b/tests/forms/RequirementsTest.php
@@ -18,8 +18,10 @@ class RequirementsTest extends SapphireTest {
$backend->javascript('http://www.mydomain.com/test.js');
$backend->javascript('https://www.mysecuredomain.com/test.js');
+ $backend->javascript('//scheme-relative.example.com/test.js');
$backend->css('http://www.mydomain.com/test.css');
$backend->css('https://www.mysecuredomain.com/test.css');
+ $backend->css('//scheme-relative.example.com/test.css');
$html = $backend->includeInHTML(false, self::$html_template);
@@ -31,6 +33,10 @@ class RequirementsTest extends SapphireTest {
(strpos($html, 'https://www.mysecuredomain.com/test.js') !== false),
'Load external secure javascript URL'
);
+ $this->assertTrue(
+ (strpos($html, '//scheme-relative.example.com/test.js') !== false),
+ 'Load external scheme-relative javascript URL'
+ );
$this->assertTrue(
(strpos($html, 'http://www.mydomain.com/test.css') !== false),
'Load external CSS URL'
@@ -39,6 +45,10 @@ class RequirementsTest extends SapphireTest {
(strpos($html, 'https://www.mysecuredomain.com/test.css') !== false),
'Load external secure CSS URL'
);
+ $this->assertTrue(
+ (strpos($html, '//scheme-relative.example.com/test.css') !== false),
+ 'Load scheme-relative CSS URL'
+ );
}
protected function setupCombinedRequirements($backend) {
diff --git a/tests/model/DataObjectLazyLoadingTest.php b/tests/model/DataObjectLazyLoadingTest.php
index 8ade64c9c..4b555f9f9 100644
--- a/tests/model/DataObjectLazyLoadingTest.php
+++ b/tests/model/DataObjectLazyLoadingTest.php
@@ -28,8 +28,10 @@ class DataObjectLazyLoadingTest extends SapphireTest {
$expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."Created", ' .
'"DataObjectTest_Team"."LastEdited", "DataObjectTest_Team"."ID", CASE WHEN '.
'"DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' .
- $db->prepStringForDB('DataObjectTest_Team').' END AS "RecordClassName" FROM "DataObjectTest_Team" WHERE ' .
- '("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').'))';
+ $db->prepStringForDB('DataObjectTest_Team').' END AS "RecordClassName", "DataObjectTest_Team"."Title" '.
+ 'FROM "DataObjectTest_Team" ' .
+ 'WHERE ("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').'))' .
+ ' ORDER BY "DataObjectTest_Team"."Title" ASC';
$this->assertEquals($expected, $playerList->sql());
}
@@ -43,7 +45,8 @@ class DataObjectLazyLoadingTest extends SapphireTest {
'"DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' .
$db->prepStringForDB('DataObjectTest_Team').' END AS "RecordClassName" FROM "DataObjectTest_Team" LEFT JOIN ' .
'"DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = "DataObjectTest_Team"."ID" WHERE ' .
- '("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').'))';
+ '("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').')) ' .
+ 'ORDER BY "DataObjectTest_Team"."Title" ASC';
$this->assertEquals($expected, $playerList->sql());
}
@@ -55,7 +58,8 @@ class DataObjectLazyLoadingTest extends SapphireTest {
'"DataObjectTest_Team"."LastEdited", "DataObjectTest_Team"."Title", "DataObjectTest_Team"."ID", ' .
'CASE WHEN "DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' .
$db->prepStringForDB('DataObjectTest_Team').' END AS "RecordClassName" FROM "DataObjectTest_Team" WHERE ' .
- '("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').'))';
+ '("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').')) ' .
+ 'ORDER BY "DataObjectTest_Team"."Title" ASC';
$this->assertEquals($expected, $playerList->sql());
}
@@ -66,9 +70,10 @@ class DataObjectLazyLoadingTest extends SapphireTest {
$expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."Created", ' .
'"DataObjectTest_Team"."LastEdited", "DataObjectTest_SubTeam"."SubclassDatabaseField", ' .
'"DataObjectTest_Team"."ID", CASE WHEN "DataObjectTest_Team"."ClassName" IS NOT NULL THEN ' .
- '"DataObjectTest_Team"."ClassName" ELSE '.$db->prepStringForDB('DataObjectTest_Team').' END AS "RecordClassName" FROM ' .
+ '"DataObjectTest_Team"."ClassName" ELSE '.$db->prepStringForDB('DataObjectTest_Team').' END AS "RecordClassName", "DataObjectTest_Team"."Title" FROM ' .
'"DataObjectTest_Team" LEFT JOIN "DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = ' .
- '"DataObjectTest_Team"."ID" WHERE ("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').'))';
+ '"DataObjectTest_Team"."ID" WHERE ("DataObjectTest_Team"."ClassName" IN ('.$db->prepStringForDB('DataObjectTest_SubTeam').')) ' .
+ 'ORDER BY "DataObjectTest_Team"."Title" ASC';
$this->assertEquals($expected, $playerList->sql());
}
diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php
index 0d88f0ace..344c9d3ee 100644
--- a/tests/model/DataObjectTest.php
+++ b/tests/model/DataObjectTest.php
@@ -1111,6 +1111,8 @@ class DataObjectTest_Team extends DataObject implements TestOnly {
)
);
+ static $default_sort = "Title";
+
function MyTitle() {
return 'Team ' . $this->Title;
}
diff --git a/view/Requirements.php b/view/Requirements.php
index 005ef1997..46702088e 100644
--- a/view/Requirements.php
+++ b/view/Requirements.php
@@ -781,7 +781,7 @@ class Requirements_Backend {
* @return string|boolean
*/
protected function path_for_file($fileOrUrl) {
- if(preg_match('/^http[s]?/', $fileOrUrl)) {
+ if(preg_match('{^//|http[s]?}', $fileOrUrl)) {
return $fileOrUrl;
} elseif(Director::fileExists($fileOrUrl)) {
$prefix = Director::baseURL();