mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
(merged from branches/roa. use "svn log -c <changeset> -g <module-svn-path>" for detailed commit message)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60227 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
c1178c31ce
commit
8fd1a33d84
@ -6,15 +6,40 @@
|
||||
*/
|
||||
|
||||
abstract class DataFormatter extends Object {
|
||||
|
||||
/**
|
||||
* Set priority from 0-100.
|
||||
* If multiple formatters for the same extension exist,
|
||||
* we select the one with highest priority.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $priority = 50;
|
||||
|
||||
/**
|
||||
* Follow relations for the {@link DataObject} instances
|
||||
* ($has_one, $has_many, $many_many).
|
||||
* Set to "0" to disable relation output.
|
||||
*
|
||||
* @todo Support more than one nesting level
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $relationDepth = 1;
|
||||
|
||||
/**
|
||||
* Get a DataFormatter object suitable for handling the given file extension
|
||||
*/
|
||||
static function for_extension($extension) {
|
||||
$classes = ClassInfo::subclassesFor("DataFormatter");
|
||||
array_shift($classes);
|
||||
|
||||
$sortedClasses = array();
|
||||
foreach($classes as $class) {
|
||||
$formatter = singleton($class);
|
||||
$sortedClasses[$class] = singleton($class)->stat('priority');
|
||||
}
|
||||
arsort($sortedClasses);
|
||||
foreach($sortedClasses as $className => $priority) {
|
||||
$formatter = singleton($className);
|
||||
if(in_array($extension, $formatter->supportedExtensions())) {
|
||||
return $formatter;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
$id = $obj->ID;
|
||||
|
||||
$json = "{\n className : \"$className\",\n";
|
||||
$dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
|
||||
$dbFields = array_merge($obj->inheritedDatabaseFields(), array('ID'=>'Int'));
|
||||
foreach($dbFields as $fieldName => $fieldType) {
|
||||
if(is_object($obj->$fieldName)) {
|
||||
$jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON();
|
||||
@ -31,6 +31,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
if($this->relationDepth > 0) {
|
||||
foreach($obj->has_one() as $relName => $relClass) {
|
||||
$fieldName = $relName . 'ID';
|
||||
if($obj->$fieldName) {
|
||||
@ -62,6 +63,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
}
|
||||
$jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . "\n ]";
|
||||
}
|
||||
}
|
||||
|
||||
return "{\n " . implode(",\n ", $jsonParts) . "\n}"; }
|
||||
|
||||
|
@ -122,7 +122,14 @@ class RestfulServer extends Controller {
|
||||
* @return String The serialized representation of the requested object(s) - usually XML or JSON.
|
||||
*/
|
||||
protected function getHandler($className, $id, $relation, $formatter) {
|
||||
$limit = (int)$this->request->getVar('limit');
|
||||
$sort = array(
|
||||
'sort' => $this->request->getVar('sort'),
|
||||
'dir' => $this->request->getVar('dir')
|
||||
);
|
||||
$limit = array(
|
||||
'start' => $this->request->getVar('start'),
|
||||
'limit' => $this->request->getVar('limit')
|
||||
);
|
||||
|
||||
if($id) {
|
||||
$obj = DataObject::get_by_id($className, $id);
|
||||
@ -135,12 +142,27 @@ class RestfulServer extends Controller {
|
||||
}
|
||||
|
||||
if($relation) {
|
||||
if($obj->hasMethod($relation)) $obj = $obj->$relation('', '', '', $limit);
|
||||
else return $this->notFound();
|
||||
if($relationClass = $obj->many_many($relation)) {
|
||||
$query = $obj->getManyManyComponentsQuery($relation);
|
||||
} elseif($relationClass = $obj->has_many($relation)) {
|
||||
$query = $obj->getComponentsQuery($relation);
|
||||
} elseif($relationClass = $obj->has_one($relation)) {
|
||||
$query = null;
|
||||
} elseif($obj->hasMethod("{$relation}Query")) {
|
||||
// @todo HACK Switch to ComponentSet->getQuery() once we implement it (and lazy loading)
|
||||
$query = $obj->{"{$relation}Query"}(null, $sort, null, $limit);
|
||||
$relationClass = $obj->{"{$relation}Class"}();
|
||||
} else {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
// get all results
|
||||
$obj = $this->search($relationClass, $this->request->getVars(), $sort, $limit, $query);
|
||||
if(!$obj) $obj = new DataObjectSet();
|
||||
}
|
||||
|
||||
} else {
|
||||
$obj = DataObject::get($className, "");
|
||||
$obj = $this->search($className, $this->request->getVars(), $sort, $limit);
|
||||
// show empty serialized result when no records are present
|
||||
if(!$obj) $obj = new DataObjectSet();
|
||||
if(!singleton($className)->stat('api_access')) {
|
||||
@ -152,6 +174,25 @@ class RestfulServer extends Controller {
|
||||
else return $formatter->convertDataObject($obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the default {@link SearchContext} specified through
|
||||
* {@link DataObject::getDefaultSearchContext()} to augument
|
||||
* an existing query object (mostly a component query from {@link DataObject})
|
||||
* with search clauses.
|
||||
*
|
||||
* @todo Allow specifying of different searchcontext getters on model-by-model basis
|
||||
*
|
||||
* @param string $className
|
||||
* @param array $params
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
protected function search($className, $params = null, $sort = null, $limit = null, $existingQuery = null) {
|
||||
$searchContext = singleton($className)->getDefaultSearchContext();
|
||||
$query = $searchContext->getQuery($params, $sort, $limit, $existingQuery);
|
||||
|
||||
return singleton($className)->buildDataObjectSet($query->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for object delete
|
||||
*/
|
||||
@ -195,7 +236,6 @@ class RestfulServer extends Controller {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restful server handler for a DataObjectSet
|
||||
*/
|
||||
|
@ -29,7 +29,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
$objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
|
||||
|
||||
$json = "<$className href=\"$objHref.xml\">\n";
|
||||
$dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
|
||||
$dbFields = array_merge($obj->inheritedDatabaseFields(), array('ID'=>'Int'));
|
||||
foreach($dbFields as $fieldName => $fieldType) {
|
||||
if(is_object($obj->$fieldName)) {
|
||||
$json .= $obj->$fieldName->toXML();
|
||||
@ -38,7 +38,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($this->relationDepth > 0) {
|
||||
foreach($obj->has_one() as $relName => $relClass) {
|
||||
$fieldName = $relName . 'ID';
|
||||
if($obj->$fieldName) {
|
||||
@ -69,6 +69,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
}
|
||||
$json .= "</$relName>\n";
|
||||
}
|
||||
}
|
||||
|
||||
$json .= "</$className>";
|
||||
|
||||
|
@ -840,9 +840,9 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
*
|
||||
* @param string $componentName Name of the component
|
||||
* @param string $filter A filter to be inserted into the WHERE clause
|
||||
* @param string $sort A sort expression to be inserted into the ORDER BY clause. If omitted, the static field $default_sort on the component class will be used.
|
||||
* @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, the static field $default_sort on the component class will be used.
|
||||
* @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
|
||||
* @param string $limit A limit expression to be inserted into the LIMIT clause
|
||||
* @param string|array $limit A limit expression to be inserted into the LIMIT clause
|
||||
*
|
||||
* @return ComponentSet The components of the one-to-many relationship.
|
||||
*/
|
||||
@ -861,14 +861,9 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
$joinField = $this->getComponentJoinField($componentName);
|
||||
|
||||
if($this->isInDB()) { //Check to see whether we should query the db
|
||||
$componentObj = singleton($componentClass);
|
||||
$id = $this->getField("ID");
|
||||
|
||||
// get filter
|
||||
$combinedFilter = "$joinField = '$id'";
|
||||
if($filter) $combinedFilter .= " AND {$filter}";
|
||||
|
||||
$result = $componentObj->instance_get($combinedFilter, $sort, $join, $limit, "ComponentSet");
|
||||
$query = $this->getComponentsQuery($componentName, $filter, $sort, $join, $limit);
|
||||
$result = $this->buildDataObjectSet($query->execute(), 'ComponentSet', $query, $componentClass);
|
||||
if($result) $result->parseQueryLimit($query);
|
||||
}
|
||||
|
||||
if(!$result) {
|
||||
@ -883,6 +878,37 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query object for a $has_many Component.
|
||||
*
|
||||
* Use {@link DataObjectSet->setComponentInfo()} to attach metadata to the
|
||||
* resultset you're building with this query.
|
||||
* Use {@link DataObject->buildDataObjectSet()} to build a set out of the {@link SQLQuery}
|
||||
* object, and pass "ComponentSet" as a $containerClass.
|
||||
*
|
||||
* @param string $componentName
|
||||
* @param string $filter
|
||||
* @param string|array $sort
|
||||
* @param string $join
|
||||
* @param string|array $limit
|
||||
* @return SQLQuery
|
||||
*/
|
||||
public function getComponentsQuery($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
|
||||
if(!$componentClass = $this->has_many($componentName)) {
|
||||
user_error("DataObject::getComponentsQuery(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
|
||||
}
|
||||
|
||||
$joinField = $this->getComponentJoinField($componentName);
|
||||
|
||||
$id = $this->getField("ID");
|
||||
|
||||
// get filter
|
||||
$combinedFilter = "$joinField = '$id'";
|
||||
if($filter) $combinedFilter .= " AND {$filter}";
|
||||
|
||||
return singleton($componentClass)->extendedSQL($combinedFilter, $sort, $limit, $join);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find the db-key for storing a relation (defaults to "ParentID" if no relation is found).
|
||||
* The iteration is necessary because the most specific class does not always have a database-table.
|
||||
@ -893,7 +919,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
*/
|
||||
public function getComponentJoinField($componentName) {
|
||||
if(!$componentClass = $this->has_many($componentName)) {
|
||||
user_error("DataObject::getComsponents(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
|
||||
user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
|
||||
}
|
||||
$componentObj = singleton($componentClass);
|
||||
|
||||
@ -947,24 +973,14 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
|
||||
|
||||
if($this->ID && is_numeric($this->ID)) {
|
||||
if($componentClass) {
|
||||
$componentObj = singleton($componentClass);
|
||||
|
||||
// Join expression is done on SiteTree.ID even if we link to Page; it helps work around
|
||||
// database inconsistencies
|
||||
$componentBaseClass = ClassInfo::baseDataClass($componentClass);
|
||||
|
||||
$query = $componentObj->extendedSQL(
|
||||
"`$table`.$parentField = $this->ID", // filter
|
||||
$sort,
|
||||
$limit,
|
||||
"INNER JOIN `$table` ON `$table`.$componentField = `$componentBaseClass`.ID" // join
|
||||
);
|
||||
array_unshift($query->select, "`$table`.*");
|
||||
if($this->ID && is_numeric($this->ID)) {
|
||||
|
||||
if($filter) $query->where[] = $filter;
|
||||
if($join) $query->from[] = $join;
|
||||
if($componentClass) {
|
||||
$query = $this->getManyManyComponentsQuery($componentName, $filter, $sort, $join, $limit);
|
||||
|
||||
$records = $query->execute();
|
||||
$result = $this->buildDataObjectSet($records, "ComponentSet", $query, $componentBaseClass);
|
||||
@ -987,6 +1003,43 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query object for a $many_many Component.
|
||||
* Use {@link DataObjectSet->setComponentInfo()} to attach metadata to the
|
||||
* resultset you're building with this query.
|
||||
* Use {@link DataObject->buildDataObjectSet()} to build a set out of the {@link SQLQuery}
|
||||
* object, and pass "ComponentSet" as a $containerClass.
|
||||
*
|
||||
* @param string $componentName
|
||||
* @param string $filter
|
||||
* @param string|array $sort
|
||||
* @param string $join
|
||||
* @param string|array $limit
|
||||
* @return SQLQuery
|
||||
*/
|
||||
public function getManyManyComponentsQuery($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
|
||||
|
||||
$componentObj = singleton($componentClass);
|
||||
|
||||
// Join expression is done on SiteTree.ID even if we link to Page; it helps work around
|
||||
// database inconsistencies
|
||||
$componentBaseClass = ClassInfo::baseDataClass($componentClass);
|
||||
|
||||
$query = $componentObj->extendedSQL(
|
||||
"`$table`.$parentField = $this->ID", // filter
|
||||
$sort,
|
||||
$limit,
|
||||
"INNER JOIN `$table` ON `$table`.$componentField = `$componentBaseClass`.ID" // join
|
||||
);
|
||||
array_unshift($query->select, "`$table`.*");
|
||||
|
||||
if($filter) $query->where[] = $filter;
|
||||
if($join) $query->from[] = $join;
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty component for the given one-one or one-many relationship
|
||||
*
|
||||
@ -1004,6 +1057,61 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the scaffold-generated relation fields to the given field set
|
||||
*/
|
||||
protected function addScaffoldRelationFields($fieldSet) {
|
||||
if ($this->has_many() || $this->_many_many()) {
|
||||
$oldFields = $fieldSet;
|
||||
$fieldSet = new FieldSet(
|
||||
new TabSet("Root", new Tab("Main"))
|
||||
);
|
||||
foreach($oldFields as $field) {
|
||||
$fieldSet->addFieldToTab("Root.Main", $field);
|
||||
}
|
||||
}
|
||||
if($this->has_many()) {
|
||||
// Add each relation as a separate tab
|
||||
foreach($this->has_many() as $relationship => $component) {
|
||||
$relationshipFields = singleton($component)->summary_fields();
|
||||
$foreignKey = $this->getComponentJoinField($relationship);
|
||||
$fieldSet->addFieldToTab("Root.$relationship", new ComplexTableField($this, $relationship, $component, $relationshipFields, "getCMSFields", "$foreignKey = $this->ID"));
|
||||
}
|
||||
}
|
||||
if ($this->many_many()) {
|
||||
foreach($this->many_many() as $relationship => $component) {
|
||||
$relationshipFields = singleton($component)->summary_fields();
|
||||
$filterJoin = $this->getManyManyJoin($relationship, $component);
|
||||
$tableField = new ComplexTableField($this, $relationship, $component, $relationshipFields, "getCMSFields", '', '', $filterJoin);
|
||||
$tableField->popupClass = "ScaffoldingComplexTableField_Popup";
|
||||
$fieldSet->addFieldToTab("Root.$relationship", $tableField);
|
||||
}
|
||||
}
|
||||
return $fieldSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull out a join clause for a many-many relationship.
|
||||
*
|
||||
* @param string $componentName The many_many or belongs_many_many relation to join to.
|
||||
* @param string $baseTable The classtable that will already be included in the SQL query to which this join will be added.
|
||||
* @return string SQL join clause
|
||||
*/
|
||||
function getManyManyJoin($componentName, $baseTable) {
|
||||
if(!$componentClass = $this->many_many($componentName)) {
|
||||
user_error("DataObject::getComponents(): Unknown many-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
|
||||
}
|
||||
$classes = array_reverse(ClassInfo::ancestry($this->class));
|
||||
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
|
||||
|
||||
if($baseTable == $parentClass) {
|
||||
return "LEFT JOIN `$table` ON (`$parentField` = `$parentClass`.`ID` AND `$componentField` = '{$this->ID}')";
|
||||
} else {
|
||||
return "LEFT JOIN `$table` ON (`$componentField` = `$componentClass`.`ID` AND `$parentField` = '{$this->ID}')";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class of a one-to-one component. If $component is null, return all of the one-to-one components and their classes.
|
||||
*
|
||||
@ -1225,29 +1333,6 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the scaffold-generated relation fields to the given field set
|
||||
*/
|
||||
protected function addScaffoldRelationFields($fieldSet) {
|
||||
|
||||
if($this->has_many()) {
|
||||
// Refactor the fields that we have been given into a tab, "Main", in a tabset
|
||||
$oldFields = $fieldSet;
|
||||
$fieldSet = new FieldSet(
|
||||
new TabSet("Root", new Tab("Main"))
|
||||
);
|
||||
foreach($oldFields as $field) $fieldSet->addFieldToTab("Root.Main", $field);
|
||||
|
||||
// Add each relation as a separate tab
|
||||
foreach($this->has_many() as $relationship => $component) {
|
||||
$relationshipFields = singleton($component)->summary_fields();
|
||||
$foreignKey = $this->getComponentJoinField($relationship);
|
||||
$fieldSet->addFieldToTab("Root.$relationship", new ComplexTableField($this, $relationship, $component, $relationshipFields, "getCMSFields", "$foreignKey = $this->ID"));
|
||||
}
|
||||
}
|
||||
return $fieldSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Centerpiece of every data administration interface in Silverstripe,
|
||||
* which returns a {@link FieldSet} suitable for a {@link Form} object.
|
||||
@ -1643,8 +1728,8 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
* Build a {@link SQLQuery} object to perform the given query.
|
||||
*
|
||||
* @param string $filter A filter to be inserted into the WHERE clause.
|
||||
* @param string $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
|
||||
* @param string $limit A limit expression to be inserted into the LIMIT clause.
|
||||
* @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
|
||||
* @param string|array $limit A limit expression to be inserted into the LIMIT clause.
|
||||
* @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
|
||||
* @param boolean $restictClasses Restrict results to only objects of either this class of a subclass of this class
|
||||
* @param string $having A filter to be inserted into the HAVING clause.
|
||||
@ -1666,42 +1751,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
$baseClass = array_shift($tableClasses);
|
||||
$select = array("`$baseClass`.*");
|
||||
|
||||
// If sort contains a function call, let's move the sort clause into a separate selected field.
|
||||
// Some versions of MySQL choke if you have a group function referenced directly in the ORDER BY
|
||||
if($sort && strpos($sort,'(') !== false) {
|
||||
// Sort can be "Col1 DESC|ASC, Col2 DESC|ASC", we need to handle that
|
||||
$sortParts = explode(",", $sort);
|
||||
|
||||
// If you have select if(X,A,B),C then the array will return 'if(X','A','B)','C'.
|
||||
// Turn this into 'if(X,A,B)','C' by counting brackets
|
||||
while(list($i,$sortPart) = each($sortParts)) {
|
||||
while(substr_count($sortPart,'(') > substr_count($sortPart,')')) {
|
||||
list($i,$nextSortPart) = each($sortParts);
|
||||
if($i === null) break;
|
||||
$sortPart .= ',' . $nextSortPart;
|
||||
}
|
||||
$lumpedSortParts[] = $sortPart;
|
||||
}
|
||||
|
||||
foreach($lumpedSortParts as $i => $sortPart) {
|
||||
$sortPart = trim($sortPart);
|
||||
if(substr(strtolower($sortPart),-5) == ' desc') {
|
||||
$select[] = substr($sortPart,0,-5) . " AS _SortColumn{$i}";
|
||||
$newSorts[] = "_SortColumn{$i} DESC";
|
||||
} else if(substr(strtolower($sortPart),-4) == ' asc') {
|
||||
$select[] = substr($sortPart,0,-4) . " AS _SortColumn{$i}";
|
||||
$newSorts[] = "_SortColumn{$i} ASC";
|
||||
} else {
|
||||
$select[] = "$sortPart AS _SortColumn{$i}";
|
||||
$newSorts[] = "_SortColumn{$i} ASC";
|
||||
}
|
||||
}
|
||||
|
||||
$sort = implode(", ", $newSorts);
|
||||
}
|
||||
|
||||
// Build our intial query
|
||||
$query = new SQLQuery($select, "`$baseClass`", $filter, $sort);
|
||||
$query = new SQLQuery($select);
|
||||
$query->from("`$baseClass`");
|
||||
$query->where($filter);
|
||||
$query->orderby($sort);
|
||||
$query->limit($limit);
|
||||
|
||||
// Add SQL for multi-value fields on the base table
|
||||
$databaseFields = $this->databaseFields();
|
||||
@ -1712,7 +1767,6 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all the tables
|
||||
if($tableClasses) {
|
||||
foreach($tableClasses as $tableClass) {
|
||||
@ -1752,10 +1806,6 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
$query->where[] = "`$baseClass`.ClassName IN ('" . implode("','", $classNames) . "')";
|
||||
}
|
||||
|
||||
if($limit) {
|
||||
$query->limit = $limit;
|
||||
}
|
||||
|
||||
if($having) {
|
||||
$query->having[] = $having;
|
||||
}
|
||||
@ -1772,8 +1822,8 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
* Like {@link buildSQL}, but applies the extension modifications.
|
||||
*
|
||||
* @param string $filter A filter to be inserted into the WHERE clause.
|
||||
* @param string $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
|
||||
* @param string $limit A limit expression to be inserted into the LIMIT clause.
|
||||
* @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
|
||||
* @param string|array $limit A limit expression to be inserted into the LIMIT clause.
|
||||
* @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
|
||||
* @param string $having A filter to be inserted into the HAVING clause.
|
||||
*
|
||||
@ -1808,9 +1858,9 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
*
|
||||
* @param string $callerClass The class of objects to be returned
|
||||
* @param string $filter A filter to be inserted into the WHERE clause.
|
||||
* @param string $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
|
||||
* @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
|
||||
* @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
|
||||
* @param string $limit A limit expression to be inserted into the LIMIT clause.
|
||||
* @param string|array $limit A limit expression to be inserted into the LIMIT clause.
|
||||
* @param string $containerClass The container class to return the results in.
|
||||
*
|
||||
* @return mixed The objects matching the filter, in the class specified by $containerClass
|
||||
@ -2153,7 +2203,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
||||
$fields = array();
|
||||
$currentObj = $this;
|
||||
while(get_class($currentObj) != 'DataObject') {
|
||||
$fields = array_merge($fields, $currentObj->customDatabaseFields());
|
||||
$fields = array_merge($fields, (array)$currentObj->customDatabaseFields());
|
||||
$currentObj = singleton($currentObj->parentClass());
|
||||
}
|
||||
return $fields;
|
||||
|
@ -15,19 +15,19 @@ class SQLQuery extends Object {
|
||||
* An array of fields to select.
|
||||
* @var array
|
||||
*/
|
||||
public $select;
|
||||
public $select = array();
|
||||
|
||||
/**
|
||||
* An array of join clauses. The first one is just the table name.
|
||||
* @var array
|
||||
*/
|
||||
public $from;
|
||||
public $from = array();
|
||||
|
||||
/**
|
||||
* An array of filters.
|
||||
* @var array
|
||||
*/
|
||||
public $where;
|
||||
public $where = array();
|
||||
|
||||
/**
|
||||
* An ORDER BY clause.
|
||||
@ -39,13 +39,13 @@ class SQLQuery extends Object {
|
||||
* An array of fields to group by.
|
||||
* @var array
|
||||
*/
|
||||
public $groupby;
|
||||
public $groupby = array();
|
||||
|
||||
/**
|
||||
* An array of having clauses.
|
||||
* @var array
|
||||
*/
|
||||
public $having;
|
||||
public $having = array();
|
||||
|
||||
/**
|
||||
* A limit clause.
|
||||
@ -57,13 +57,13 @@ class SQLQuery extends Object {
|
||||
* If this is true DISTINCT will be added to the SQL.
|
||||
* @var boolean
|
||||
*/
|
||||
public $distinct;
|
||||
public $distinct = false;
|
||||
|
||||
/**
|
||||
* If this is true, this statement will delete rather than select.
|
||||
* @var boolean
|
||||
*/
|
||||
public $delete;
|
||||
public $delete = false;
|
||||
|
||||
/**
|
||||
* The logical connective used to join WHERE clauses. Defaults to AND.
|
||||
@ -83,13 +83,14 @@ class SQLQuery extends Object {
|
||||
* @param string $limit A LIMIT clause.
|
||||
*/
|
||||
function __construct($select = "*", $from = array(), $where = "", $orderby = "", $groupby = "", $having = "", $limit = "") {
|
||||
if($select) $this->select = is_array($select) ? $select : array($select);
|
||||
if($from) $this->from = is_array($from) ? $from : array(str_replace('`','',$from) => $from);
|
||||
if($where) $this->where = is_array($where) ? $where : array($where);
|
||||
$this->orderby = $orderby;
|
||||
if($groupby) $this->groupby = is_array($groupby) ? $groupby : array($groupby);
|
||||
if($having) $this->having = is_array($having) ? $having : array($having);
|
||||
$this->limit = $limit;
|
||||
$this->select($select);
|
||||
// @todo
|
||||
$this->from = is_array($from) ? $from : array(str_replace('`','',$from) => $from);
|
||||
$this->where($where);
|
||||
$this->orderby($orderby);
|
||||
$this->groupby($groupby);
|
||||
$this->having($having);
|
||||
$this->limit($limit);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
@ -114,6 +115,7 @@ class SQLQuery extends Object {
|
||||
} else {
|
||||
$this->select = is_array($fields) ? $fields : array($fields);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -125,18 +127,147 @@ class SQLQuery extends Object {
|
||||
* </code>
|
||||
*
|
||||
* @param string $table
|
||||
* @return SQLQuery
|
||||
* @return SQLQuery This instance
|
||||
*/
|
||||
public function from($table) {
|
||||
$this->from[] = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a LEFT JOIN criteria to the FROM clause.
|
||||
*
|
||||
* @return SQLQuery This instance
|
||||
*/
|
||||
public function leftJoin($table, $onPredicate) {
|
||||
$this->from[] = "LEFT JOIN $table ON $onPredicate";
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass LIMIT clause either as SQL snippet or in array format.
|
||||
*
|
||||
* @param string|array $limit
|
||||
* @return SQLQuery This instance
|
||||
*/
|
||||
public function limit($limit) {
|
||||
// Pass limit as array or SQL string value
|
||||
if(is_array($limit)) {
|
||||
if(!array_key_exists('limit',$limit)) user_error('SQLQuery::limit(): Wrong format for $limit', E_USER_ERROR);
|
||||
|
||||
if(isset($limit['start']) && is_numeric($limit['start']) && isset($limit['limit']) && is_numeric($limit['limit'])) {
|
||||
// @todo MySQL specific LIMIT syntax
|
||||
$combinedLimit = (int)$limit['start'] . ',' . (int)$limit['limit'];
|
||||
} elseif(isset($limit['limit']) && is_numeric($limit['limit'])) {
|
||||
$combinedLimit = (int)$limit['limit'];
|
||||
} else {
|
||||
$combinedLimit = false;
|
||||
}
|
||||
} else {
|
||||
$combinedLimit = $limit;
|
||||
}
|
||||
|
||||
if(!empty($combinedLimit)) $this->limit = $combinedLimit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass ORDER BY clause either as SQL snippet or in array format.
|
||||
*
|
||||
* @todo Implement passing of multiple orderby pairs in nested array syntax,
|
||||
* e.g. array(array('sort'=>'A','dir'=>'asc'),array('sort'=>'B'))
|
||||
*
|
||||
* @param string|array $orderby
|
||||
* @return SQLQuery This instance
|
||||
*/
|
||||
public function orderby($orderby) {
|
||||
// if passed as an array, assume two array values with column and direction (asc|desc)
|
||||
if(is_array($orderby)) {
|
||||
if(!array_key_exists('sort', $orderby)) user_error('SQLQuery::orderby(): Wrong format for $orderby array', E_USER_ERROR);
|
||||
|
||||
if(isset($orderby['sort']) && !empty($orderby['sort']) && isset($orderby['dir']) && !empty($orderby['dir'])) {
|
||||
$combinedOrderby = "`" . Convert::raw2sql($orderby['sort']) . "` " . Convert::raw2sql(strtoupper($orderby['dir']));
|
||||
} elseif(isset($orderby['sort']) && !empty($orderby['sort'])) {
|
||||
$combinedOrderby = "`" . Convert::raw2sql($orderby['sort']) . "`";
|
||||
} else {
|
||||
$combinedOrderby = false;
|
||||
}
|
||||
} else {
|
||||
$combinedOrderby = $orderby;
|
||||
}
|
||||
|
||||
// If sort contains a function call, let's move the sort clause into a separate selected field.
|
||||
// Some versions of MySQL choke if you have a group function referenced directly in the ORDER BY
|
||||
if($combinedOrderby && strpos($combinedOrderby,'(') !== false) {
|
||||
// Sort can be "Col1 DESC|ASC, Col2 DESC|ASC", we need to handle that
|
||||
$sortParts = explode(",", $combinedOrderby);
|
||||
|
||||
// If you have select if(X,A,B),C then the array will return 'if(X','A','B)','C'.
|
||||
// Turn this into 'if(X,A,B)','C' by counting brackets
|
||||
while(list($i,$sortPart) = each($sortParts)) {
|
||||
while(substr_count($sortPart,'(') > substr_count($sortPart,')')) {
|
||||
list($i,$nextSortPart) = each($sortParts);
|
||||
if($i === null) break;
|
||||
$sortPart .= ',' . $nextSortPart;
|
||||
}
|
||||
$lumpedSortParts[] = $sortPart;
|
||||
}
|
||||
|
||||
foreach($lumpedSortParts as $i => $sortPart) {
|
||||
$sortPart = trim($sortPart);
|
||||
if(substr(strtolower($sortPart),-5) == ' desc') {
|
||||
$select[] = substr($sortPart,0,-5) . " AS _SortColumn{$i}";
|
||||
$newSorts[] = "_SortColumn{$i} DESC";
|
||||
} else if(substr(strtolower($sortPart),-4) == ' asc') {
|
||||
$select[] = substr($sortPart,0,-4) . " AS _SortColumn{$i}";
|
||||
$newSorts[] = "_SortColumn{$i} ASC";
|
||||
} else {
|
||||
$select[] = "$sortPart AS _SortColumn{$i}";
|
||||
$newSorts[] = "_SortColumn{$i} ASC";
|
||||
}
|
||||
}
|
||||
|
||||
$combinedOrderby = implode(", ", $newSorts);
|
||||
}
|
||||
|
||||
if(!empty($combinedOrderby)) $this->orderby = $combinedOrderby;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a GROUP BY clause.
|
||||
*
|
||||
* @param string|array $groupby
|
||||
* @return SQLQuery
|
||||
*/
|
||||
public function groupby($groupby) {
|
||||
if(is_array($groupby)) {
|
||||
$this->groupby = array_merge($this->groupby, $groupby);
|
||||
} elseif(!empty($groupby)) {
|
||||
$this->groupby[] = $groupby;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a HAVING clause.
|
||||
*
|
||||
* @param string|array $having
|
||||
* @return SQLQuery
|
||||
*/
|
||||
public function having($having) {
|
||||
if(is_array($having)) {
|
||||
$this->having = array_merge($this->having, $having);
|
||||
} elseif(!empty($having)) {
|
||||
$this->having[] = $having;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,7 +297,13 @@ class SQLQuery extends Object {
|
||||
} else {
|
||||
$filter = $args[0];
|
||||
}
|
||||
|
||||
if(is_array($filter)) {
|
||||
$this->where = array_merge($this->where,$filter);
|
||||
} elseif(!empty($filter)) {
|
||||
$this->where[] = $filter;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -888,4 +888,70 @@ class ComplexTableField_Popup extends Form {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by ModelAdmin scaffolding, to manage many-many relationships.
|
||||
*/
|
||||
class ScaffoldingComplexTableField_Popup extends Form {
|
||||
protected $sourceClass;
|
||||
protected $dataObject;
|
||||
|
||||
function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
|
||||
$this->dataObject = $dataObject;
|
||||
|
||||
/**
|
||||
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
|
||||
* Some have special requirements.
|
||||
*/
|
||||
//Requirements::css('cms/css/layout.css');
|
||||
Requirements::css('jsparty/tabstrip/tabstrip.css');
|
||||
Requirements::css('sapphire/css/Form.css');
|
||||
Requirements::css('sapphire/css/ComplexTableField_popup.css');
|
||||
Requirements::css('cms/css/typography.css');
|
||||
Requirements::css('cms/css/cms_right.css');
|
||||
Requirements::css('jsparty/jquery/plugins/autocomplete/jquery.ui.autocomplete.css');
|
||||
Requirements::javascript("jsparty/prototype.js");
|
||||
Requirements::javascript("jsparty/behaviour.js");
|
||||
Requirements::javascript("jsparty/prototype_improvements.js");
|
||||
Requirements::javascript("jsparty/loader.js");
|
||||
Requirements::javascript("jsparty/tabstrip/tabstrip.js");
|
||||
Requirements::javascript("jsparty/scriptaculous/scriptaculous.js");
|
||||
Requirements::javascript("jsparty/scriptaculous/controls.js");
|
||||
Requirements::javascript("jsparty/layout_helpers.js");
|
||||
Requirements::javascript("cms/javascript/LeftAndMain.js");
|
||||
Requirements::javascript("cms/javascript/LeftAndMain_right.js");
|
||||
Requirements::javascript("sapphire/javascript/TableField.js");
|
||||
Requirements::javascript("sapphire/javascript/ComplexTableField.js");
|
||||
Requirements::javascript("sapphire/javascript/ComplexTableField_popup.js");
|
||||
// jQuery requirements (how many of these are actually needed?)
|
||||
Requirements::javascript('jsparty/jquery/jquery.js');
|
||||
Requirements::javascript('jsparty/jquery/plugins/livequery/jquery.livequery.js');
|
||||
Requirements::javascript('jsparty/jquery/ui/ui.core.js');
|
||||
Requirements::javascript('jsparty/jquery/ui/ui.tabs.js');
|
||||
Requirements::javascript('jsparty/jquery/plugins/form/jquery.form.js');
|
||||
Requirements::javascript('jsparty/jquery/plugins/dimensions/jquery.dimensions.js');
|
||||
Requirements::javascript('jsparty/jquery/plugins/autocomplete/jquery.ui.autocomplete.js');
|
||||
Requirements::javascript('sapphire/javascript/ScaffoldComplexTableField.js');
|
||||
Requirements::javascript('cms/javascript/ModelAdmin.js');
|
||||
|
||||
if($this->dataObject->hasMethod('getRequirementsForPopup')) {
|
||||
$this->dataObject->getRequirementsForPopup();
|
||||
}
|
||||
|
||||
$actions = new FieldSet();
|
||||
if(!$readonly) {
|
||||
$actions->push(
|
||||
$saveAction = new FormAction("saveComplexTableField", "Save")
|
||||
);
|
||||
$saveAction->addExtraClass('save');
|
||||
}
|
||||
|
||||
parent::__construct($controller, $name, $fields, $actions, $validator);
|
||||
}
|
||||
|
||||
function FieldHolder() {
|
||||
return $this->renderWith('ComplexTableField_Form');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -139,7 +139,7 @@ class FieldSet extends DataObjectSet {
|
||||
// Create any missing tabs
|
||||
if(!$currentPointer) {
|
||||
if(is_a($parentPointer,'TabSet')) {
|
||||
$currentPointer = new Tab($tabName);
|
||||
$currentPointer = new Tab($part);
|
||||
$parentPointer->push($currentPointer);
|
||||
} else {
|
||||
user_error("FieldSet::addFieldToTab() Tried to add a tab to a " . $parentPointer->class . " object - '$part' didn't exist.", E_USER_ERROR);
|
||||
|
@ -7,10 +7,13 @@
|
||||
class Tab extends CompositeField {
|
||||
protected $tabSet;
|
||||
|
||||
public function __construct($title) {
|
||||
public function __construct($name) {
|
||||
$args = func_get_args();
|
||||
$this->title = array_shift($args);
|
||||
$this->id = ereg_replace('[^0-9A-Za-z]+', '', $this->title);
|
||||
$name = array_shift($args);
|
||||
|
||||
$this->id = preg_replace('/[^0-9A-Za-z]+/', '', $name);
|
||||
$this->title = preg_replace('/([a-z0-9])([A-Z])/', '\\1 \\2', $name);
|
||||
$this->name = $name;
|
||||
|
||||
parent::__construct($args);
|
||||
}
|
||||
|
5
javascript/ScaffoldComplexTableField.js
Normal file
5
javascript/ScaffoldComplexTableField.js
Normal file
@ -0,0 +1,5 @@
|
||||
window.onload = function() {
|
||||
|
||||
jQuery("fieldset input:first").attr('autocomplete', 'off').autocomplete({list: ["mark rickerby", "maxwell sparks"]});
|
||||
|
||||
};
|
@ -77,19 +77,20 @@ class SearchContext extends Object {
|
||||
}
|
||||
|
||||
/**
|
||||
* @refactor move to SQLQuery
|
||||
* @todo fix hack
|
||||
*/
|
||||
protected function applyBaseTableFields() {
|
||||
$classes = ClassInfo::dataClassesFor($this->modelClass);
|
||||
//Debug::dump($classes);
|
||||
//die();
|
||||
$fields = array($classes[0].'.*', $this->modelClass.'.*');
|
||||
$fields = array($this->modelClass.'.*');
|
||||
if($this->modelClass != $classes[0]) $fields[] = $classes[0].'.*';
|
||||
//$fields = array_keys($model->db());
|
||||
$fields[] = $classes[0].'.ClassName AS RecordClassName';
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @refactor move to SQLQuery
|
||||
* @todo fix hack
|
||||
*/
|
||||
protected function applyBaseTable() {
|
||||
@ -100,6 +101,7 @@ class SearchContext extends Object {
|
||||
/**
|
||||
* @todo only works for one level deep of inheritance
|
||||
* @todo fix hack
|
||||
* @deprecated - remove me!
|
||||
*/
|
||||
protected function applyBaseTableJoin($query) {
|
||||
$classes = ClassInfo::dataClassesFor($this->modelClass);
|
||||
@ -111,28 +113,30 @@ class SearchContext extends Object {
|
||||
* list of query parameters.
|
||||
*
|
||||
* @param array $searchParams
|
||||
* @param string|array $sort Database column to sort on. Falls back to {@link DataObject::$default_sort} if not provided.
|
||||
* @param string|array $limit
|
||||
* @param SQLQuery $existingQuery
|
||||
* @return SQLQuery
|
||||
*/
|
||||
public function getQuery($searchParams, $start = false, $limit = false) {
|
||||
public function getQuery($searchParams, $sort = false, $limit = false, $existingQuery = null) {
|
||||
$model = singleton($this->modelClass);
|
||||
|
||||
$fields = $this->applyBaseTableFields($model);
|
||||
|
||||
$query = new SQLQuery($fields);
|
||||
|
||||
$baseTable = $this->applyBaseTable();
|
||||
$query->from($baseTable);
|
||||
|
||||
if($limit) $query->limit = (!empty($start)) ? "{$start},{$limit}" : $limit;
|
||||
|
||||
// SRM: This stuff is copied from DataObject,
|
||||
if($this->modelClass != $baseTable) {
|
||||
$classNames = ClassInfo::subclassesFor($this->modelClass);
|
||||
$query->where[] = "`$baseTable`.ClassName IN ('" . implode("','", $classNames) . "')";
|
||||
if($existingQuery) {
|
||||
$query = $existingQuery;
|
||||
$query->select = array_merge($query->select,$fields);
|
||||
} else {
|
||||
$query = $model->buildSQL();
|
||||
$query->select($fields);
|
||||
}
|
||||
|
||||
$SQL_limit = Convert::raw2sql($limit);
|
||||
$query->limit($SQL_limit);
|
||||
|
||||
$SQL_sort = (!empty($sort)) ? Convert::raw2sql($sort) : singleton($this->modelClass)->stat('default_sort');
|
||||
$query->orderby($SQL_sort);
|
||||
|
||||
$this->applyBaseTableJoin($query);
|
||||
|
||||
foreach($searchParams as $key => $value) {
|
||||
if ($value != '0') {
|
||||
@ -145,6 +149,7 @@ class SearchContext extends Object {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@ -154,13 +159,15 @@ class SearchContext extends Object {
|
||||
* @todo rearrange start and limit params to reflect DataObject
|
||||
*
|
||||
* @param array $searchParams
|
||||
* @param int $start
|
||||
* @param int $limit
|
||||
* @param string|array $sort
|
||||
* @param string|array $limit
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
public function getResults($searchParams, $start = false, $limit = false) {
|
||||
public function getResults($searchParams, $sort = false, $limit = false) {
|
||||
$searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields'));
|
||||
$query = $this->getQuery($searchParams, $start, $limit);
|
||||
$query = $this->getQuery($searchParams, $sort, $limit);
|
||||
|
||||
//Debug::dump($query->sql());
|
||||
|
||||
// use if a raw SQL query is needed
|
||||
$results = new DataObjectSet();
|
||||
|
@ -6,14 +6,25 @@ class DataObjectDecoratorTest extends SapphireTest {
|
||||
function testOneToManyAssociationWithDecorator() {
|
||||
$contact = new DataObjectDecoratorTest_Member();
|
||||
$contact->Website = "http://www.example.com";
|
||||
|
||||
$object = new DataObjectDecoratorTest_RelatedObject();
|
||||
$object->FieldOne = "Lorem ipsum dolor";
|
||||
$object->FieldTwo = "Random notes";
|
||||
|
||||
/* The following code doesn't currently work:
|
||||
$contact->RelatedObjects()->add($object);
|
||||
$contact->write();
|
||||
*/
|
||||
|
||||
/* Instead we have to do the following */
|
||||
$contact->write();
|
||||
$object->ContactID = $contact->ID;
|
||||
$object->write();
|
||||
|
||||
unset($contact);
|
||||
|
||||
$contact = DataObject::get_one("DataObjectDecoratorTest_Member", "Website='http://www.example.com'");
|
||||
|
||||
$this->assertType('DataObjectDecoratorTest_RelatedObject', $contact->RelatedObjects()->First());
|
||||
$this->assertEquals("Lorem ipsum dolor", $contact->RelatedObjects()->First()->FieldOne);
|
||||
$this->assertEquals("Random notes", $contact->RelatedObjects()->First()->FieldTwo);
|
||||
@ -50,11 +61,11 @@ class DataObjectDecoratorTest_RelatedObject extends DataObject implements TestOn
|
||||
|
||||
static $db = array(
|
||||
"FieldOne" => "Text",
|
||||
"FieldOne" => "Text"
|
||||
"FieldTwo" => "Text"
|
||||
);
|
||||
|
||||
static $has_one = array(
|
||||
"Contact" => "Member"
|
||||
"Contact" => "DataObjectDecoratorTest_Member"
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -55,18 +55,70 @@ class SQLQueryTest extends SapphireTest {
|
||||
|
||||
function testSelectWithPredicateFilters() {
|
||||
$query = new SQLQuery();
|
||||
$query->select(array("Name"))->from("MyTable");
|
||||
$query->select(array("Name"))->from("SQLQueryTest_DO");
|
||||
|
||||
$match = new ExactMatchFilter("Name", "Value");
|
||||
$match->setModel('SQLQueryTest_DO');
|
||||
$match->apply($query);
|
||||
|
||||
$match = new PartialMatchFilter("Meta", "Value");
|
||||
$match->setModel('SQLQueryTest_DO');
|
||||
$match->apply($query);
|
||||
$this->assertEquals("SELECT Name FROM MyTable WHERE (Name = 'Value') AND (Meta LIKE '%Value%')", $query->sql());
|
||||
|
||||
$this->assertEquals("SELECT Name FROM SQLQueryTest_DO WHERE (SQLQueryTest_DO.Name = 'Value') AND (SQLQueryTest_DO.Meta LIKE '%Value%')", $query->sql());
|
||||
}
|
||||
|
||||
function testSelectWithLimitClause() {
|
||||
// not implemented
|
||||
// numeric limit
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$query->limit("99");
|
||||
$this->assertEquals("SELECT * FROM MyTable LIMIT 99", $query->sql());
|
||||
|
||||
// array limit
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$query->limit(array('limit'=>99));
|
||||
$this->assertEquals("SELECT * FROM MyTable LIMIT 99", $query->sql());
|
||||
|
||||
// array limit with start (MySQL specific)
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$query->limit(array('limit'=>99, 'start'=>97));
|
||||
$this->assertEquals("SELECT * FROM MyTable LIMIT 97,99", $query->sql());
|
||||
}
|
||||
|
||||
function testSelectWithOrderbyClause() {
|
||||
// numeric limit
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$query->orderby('MyName ASC');
|
||||
// can't escape as we don't know if ASC or DESC is appended
|
||||
$this->assertEquals("SELECT * FROM MyTable ORDER BY MyName ASC", $query->sql());
|
||||
|
||||
// array limit
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$query->orderby(array('sort'=>'MyName'));
|
||||
$this->assertEquals("SELECT * FROM MyTable ORDER BY `MyName`", $query->sql());
|
||||
|
||||
// array limit with start (MySQL specific)
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$query->orderby(array('sort'=>'MyName','dir'=>'desc'));
|
||||
$this->assertEquals("SELECT * FROM MyTable ORDER BY `MyName` DESC", $query->sql());
|
||||
}
|
||||
|
||||
function testSelectWithComplexOrderbyClause() {
|
||||
// @todo Test "ORDER BY RANDOM() ASC,MyName DESC" etc.
|
||||
}
|
||||
}
|
||||
|
||||
class SQLQueryTest_DO extends DataObject implements TestOnly {
|
||||
static $db = array(
|
||||
"Name" => "Varchar",
|
||||
"Meta" => "Varchar",
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
@ -30,15 +30,15 @@ class SearchContextTest extends SapphireTest {
|
||||
$this->assertContains('Industry', $company->summary_fields());
|
||||
}
|
||||
|
||||
function testExactMatchUsedByDefaultWhenNotExplicitlySet() {
|
||||
function testPartialMatchUsedByDefaultWhenNotExplicitlySet() {
|
||||
$person = singleton('SearchContextTest_Person');
|
||||
$context = $person->getDefaultSearchContext();
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
"Name" => new ExactMatchFilter("Name"),
|
||||
"HairColor" => new ExactMatchFilter("HairColor"),
|
||||
"EyeColor" => new ExactMatchFilter("EyeColor")
|
||||
"Name" => new PartialMatchFilter("Name"),
|
||||
"HairColor" => new PartialMatchFilter("HairColor"),
|
||||
"EyeColor" => new PartialMatchFilter("EyeColor")
|
||||
),
|
||||
$context->getFilters()
|
||||
);
|
||||
@ -50,7 +50,7 @@ class SearchContextTest extends SapphireTest {
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
"Title" => new ExactMatchFilter("Title")
|
||||
"Title" => new PartialMatchFilter("Title")
|
||||
),
|
||||
$context->getFilters()
|
||||
);
|
||||
@ -63,7 +63,7 @@ class SearchContextTest extends SapphireTest {
|
||||
$this->assertEquals(
|
||||
array(
|
||||
"Name" => new PartialMatchFilter("Name"),
|
||||
"Industry" => new ExactMatchFilter("Industry"),
|
||||
"Industry" => new PartialMatchFilter("Industry"),
|
||||
"AnnualProfit" => new PartialMatchFilter("AnnualProfit")
|
||||
),
|
||||
$context->getFilters()
|
||||
@ -80,7 +80,7 @@ class SearchContextTest extends SapphireTest {
|
||||
|
||||
$this->assertEquals(1, $results->Count());
|
||||
|
||||
Debug::dump(DB::query("select * from SearchContextTest_Deadline")->next());
|
||||
//Debug::dump(DB::query("select * from SearchContextTest_Deadline")->next());
|
||||
|
||||
$project = $results->First();
|
||||
|
||||
@ -88,7 +88,7 @@ class SearchContextTest extends SapphireTest {
|
||||
$this->assertEquals("Blog Website", $project->Name);
|
||||
$this->assertEquals(2, $project->Actions()->Count());
|
||||
$this->assertEquals("Get RSS feeds working", $project->Actions()->First()->Description);
|
||||
Debug::dump($project->Deadline()->CompletionDate);
|
||||
//Debug::dump($project->Deadline()->CompletionDate);
|
||||
//$this->assertEquals()
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user