diff --git a/_config.php b/_config.php
index 61a5a9de4..931351e24 100644
--- a/_config.php
+++ b/_config.php
@@ -32,10 +32,7 @@ Director::addRules(10, array(
));
Director::addRules(1, array(
- '$URLSegment/$Action/$ID/$OtherID' => array(
- '_PopTokeniser' => 1,
- 'Controller' => 'ModelAsController',
- ),
+ '$URLSegment//$Action/$ID/$OtherID' => 'ModelAsController',
));
/**
diff --git a/api/DataFormatter.php b/api/DataFormatter.php
new file mode 100644
index 000000000..3295131fd
--- /dev/null
+++ b/api/DataFormatter.php
@@ -0,0 +1,42 @@
+supportedExtensions())) {
+ return $formatter;
+ }
+ }
+ }
+
+ /**
+ * Return an array of the extensions that this data formatter supports
+ */
+ abstract function supportedExtensions();
+
+
+ /**
+ * Convert a single data object to this format. Return a string.
+ * @todo Add parameters for things like selecting output columns
+ */
+ abstract function convertDataObject(DataObjectInterface $do);
+
+ /**
+ * Convert a data object set to this format. Return a string.
+ * @todo Add parameters for things like selecting output columns
+ */
+ abstract function convertDataObjectSet(DataObjectSet $set);
+
+}
\ No newline at end of file
diff --git a/api/JSONDataFormatter.php b/api/JSONDataFormatter.php
new file mode 100644
index 000000000..ef501c4d0
--- /dev/null
+++ b/api/JSONDataFormatter.php
@@ -0,0 +1,81 @@
+ header (Default: true)
+ * @return String XML
+ */
+ public function convertDataObject(DataObjectInterface $obj) {
+ $className = $obj->class;
+ $id = $obj->ID;
+
+ $json = "{\n className : \"$className\",\n";
+ $dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
+ foreach($dbFields as $fieldName => $fieldType) {
+ if(is_object($obj->$fieldName)) {
+ $jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON();
+ } else {
+ $jsonParts[] = "$fieldName : \"" . Convert::raw2js($obj->$fieldName) . "\"";
+ }
+ }
+
+ foreach($obj->has_one() as $relName => $relClass) {
+ $fieldName = $relName . 'ID';
+ if($obj->$fieldName) {
+ $href = Director::absoluteURL(self::$api_base . "$relClass/" . $obj->$fieldName);
+ } else {
+ $href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
+ }
+ $jsonParts[] = "$relName : { className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
+ }
+
+ foreach($obj->has_many() as $relName => $relClass) {
+ $jsonInnerParts = array();
+ $items = $obj->$relName();
+ foreach($items as $item) {
+ //$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
+ $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
+ $jsonInnerParts[] = "{ className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
+ }
+ $jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . " \n ]";
+ }
+
+ foreach($obj->many_many() as $relName => $relClass) {
+ $jsonInnerParts = array();
+ $items = $obj->$relName();
+ foreach($items as $item) {
+ //$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
+ $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
+ $jsonInnerParts[] = " { className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
+ }
+ $jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . "\n ]";
+ }
+
+ return "{\n " . implode(",\n ", $jsonParts) . "\n}"; }
+
+ /**
+ * Generate an XML representation of the given {@link DataObjectSet}.
+ *
+ * @param DataObjectSet $set
+ * @return String XML
+ */
+ public function convertDataObjectSet(DataObjectSet $set) {
+ $jsonParts = array();
+ foreach($set as $item) {
+ if($item->canView()) $jsonParts[] = $this->convertDataObject($item);
+ }
+ return "[\n" . implode(",\n", $jsonParts) . "\n]";
+ }
+}
\ No newline at end of file
diff --git a/api/RestfulServer.php b/api/RestfulServer.php
index c6c314cd9..602235088 100644
--- a/api/RestfulServer.php
+++ b/api/RestfulServer.php
@@ -71,16 +71,19 @@ class RestfulServer extends Controller {
'html' => 'text/html',
);
$contentType = isset($contentMap[$extension]) ? $contentMap[$extension] : 'text/xml';
-
+
+ if(!$extension) $extension = "xml";
+ $formatter = DataFormatter::for_extension($extension); //$this->dataFormatterFromMime($contentType);
+
switch($requestMethod) {
case 'GET':
- return $this->getHandler($className, $id, $relation, $contentType);
+ return $this->getHandler($className, $id, $relation, $formatter);
case 'PUT':
- return $this->putHandler($className, $id, $relation, $contentType);
+ return $this->putHandler($className, $id, $relation, $formatter);
case 'DELETE':
- return $this->deleteHandler($className, $id, $relation, $contentType);
+ return $this->deleteHandler($className, $id, $relation, $formatter);
case 'POST':
}
@@ -118,7 +121,7 @@ class RestfulServer extends Controller {
* @param String $contentType
* @return String The serialized representation of the requested object(s) - usually XML or JSON.
*/
- protected function getHandler($className, $id, $relation, $contentType) {
+ protected function getHandler($className, $id, $relation, $formatter) {
if($id) {
$obj = DataObject::get_by_id($className, $id);
if(!$obj) {
@@ -142,176 +145,10 @@ class RestfulServer extends Controller {
return $this->permissionFailure();
}
}
-
- // TO DO - inspect that Accept header as well. $_GET['accept'] can still be checked, as it's handy for debugging
- switch($contentType) {
- case "text/xml":
- $this->getResponse()->addHeader("Content-type", "text/xml");
- if($obj instanceof DataObjectSet) return $this->dataObjectSetAsXML($obj);
- else return $this->dataObjectAsXML($obj);
- case "text/json":
- //$this->getResponse()->addHeader("Content-type", "text/json");
- if($obj instanceof DataObjectSet) return $this->dataObjectSetAsJSON($obj);
- else return $this->dataObjectAsJSON($obj);
-
- case "text/html":
- case "application/xhtml+xml":
- if($obj instanceof DataObjectSet) return $this->dataObjectSetAsXHTML($obj);
- else return $this->dataObjectAsXHTML($obj);
- }
+ if($obj instanceof DataObjectSet) return $formatter->convertDataObjectSet($obj);
+ else return $formatter->convertDataObject($obj);
}
-
- /**
- * Generate an XML representation of the given {@link DataObject}.
- *
- * @param DataObject $obj
- * @param $includeHeader Include header (Default: true)
- * @return String XML
- */
- public function dataObjectAsXML(DataObject $obj, $includeHeader = true) {
- $className = $obj->class;
- $id = $obj->ID;
- $objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
-
- $json = "";
- if($includeHeader) $json .= "\n";
- $json .= "<$className href=\"$objHref.xml\">\n";
- $dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
- foreach($dbFields as $fieldName => $fieldType) {
- if(is_object($obj->$fieldName)) {
- $json .= $obj->$fieldName->toXML();
- } else {
- $json .= "<$fieldName>" . Convert::raw2xml($obj->$fieldName) . "$fieldName>\n";
- }
- }
-
-
- foreach($obj->has_one() as $relName => $relClass) {
- $fieldName = $relName . 'ID';
- if($obj->$fieldName) {
- $href = Director::absoluteURL(self::$api_base . "$relClass/" . $obj->$fieldName);
- } else {
- $href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
- }
- $json .= "<$relName linktype=\"has_one\" href=\"$href.xml\" id=\"{$obj->$fieldName}\" />\n";
- }
-
- foreach($obj->has_many() as $relName => $relClass) {
- $json .= "<$relName linktype=\"has_many\" href=\"$objHref/$relName.xml\">\n";
- $items = $obj->$relName();
- foreach($items as $item) {
- //$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
- $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
- $json .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\" />\n";
- }
- $json .= "$relName>\n";
- }
-
- foreach($obj->many_many() as $relName => $relClass) {
- $json .= "<$relName linktype=\"many_many\" href=\"$objHref/$relName.xml\">\n";
- $items = $obj->$relName();
- foreach($items as $item) {
- $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
- $json .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\" />\n";
- }
- $json .= "$relName>\n";
- }
-
- $json .= "$className>";
-
- return $json;
- }
-
- /**
- * Generate an XML representation of the given {@link DataObjectSet}.
- *
- * @param DataObjectSet $set
- * @return String XML
- */
- public function dataObjectSetAsXML(DataObjectSet $set) {
- $className = $set->class;
-
- $xml = "\n<$className>\n";
- foreach($set as $item) {
- if($item->canView()) $xml .= $this->dataObjectAsXML($item, false);
- }
- $xml .= "$className>";
-
- return $xml;
- }
-
- /**
- * Generate an JSON representation of the given {@link DataObject}.
- *
- * @see http://json.org
- *
- * @param DataObject $obj
- * @return String JSON
- */
- public function dataObjectAsJSON(DataObject $obj) {
- $className = $obj->class;
- $id = $obj->ID;
-
- $json = "{\n className : \"$className\",\n";
- $dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
- foreach($dbFields as $fieldName => $fieldType) {
- if(is_object($obj->$fieldName)) {
- $jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON();
- } else {
- $jsonParts[] = "$fieldName : \"" . Convert::raw2js($obj->$fieldName) . "\"";
- }
- }
-
- foreach($obj->has_one() as $relName => $relClass) {
- $fieldName = $relName . 'ID';
- if($obj->$fieldName) {
- $href = Director::absoluteURL(self::$api_base . "$relClass/" . $obj->$fieldName);
- } else {
- $href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
- }
- $jsonParts[] = "$relName : { className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
- }
-
- foreach($obj->has_many() as $relName => $relClass) {
- $jsonInnerParts = array();
- $items = $obj->$relName();
- foreach($items as $item) {
- //$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
- $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
- $jsonInnerParts[] = "{ className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
- }
- $jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . " \n ]";
- }
-
- foreach($obj->many_many() as $relName => $relClass) {
- $jsonInnerParts = array();
- $items = $obj->$relName();
- foreach($items as $item) {
- //$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
- $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
- $jsonInnerParts[] = " { className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
- }
- $jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . "\n ]";
- }
-
- return "{\n " . implode(",\n ", $jsonParts) . "\n}";
- }
-
- /**
- * Generate an JSON representation of the given {@link DataObjectSet}.
- *
- * @param DataObjectSet $set
- * @return String JSON
- */
- public function dataObjectSetAsJSON(DataObjectSet $set) {
- $jsonParts = array();
- foreach($set as $item) {
- if($item->canView()) $jsonParts[] = $this->dataObjectAsJSON($item);
- }
- return "[\n" . implode(",\n", $jsonParts) . "\n]";
- }
-
/**
* Handler for object delete
@@ -324,7 +161,6 @@ class RestfulServer extends Controller {
} else {
return $this->permissionFailure();
}
-
}
}
diff --git a/api/XMLDataFormatter.php b/api/XMLDataFormatter.php
new file mode 100644
index 000000000..8e4f391cd
--- /dev/null
+++ b/api/XMLDataFormatter.php
@@ -0,0 +1,96 @@
+ header (Default: true)
+ * @return String XML
+ */
+ public function convertDataObject(DataObjectInterface $obj) {
+ Controller::curr()->getResponse()->addHeader("Content-type", "text/xml");
+ return "\n" . $this->convertDataObjectWithoutHeader($obj);
+ }
+
+
+ public function convertDataObjectWithoutHeader(DataObject $obj) {
+ $className = $obj->class;
+ $id = $obj->ID;
+ $objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
+
+ $json = "<$className href=\"$objHref.xml\">\n";
+ $dbFields = array_merge($obj->databaseFields(), array('ID'=>'Int'));
+ foreach($dbFields as $fieldName => $fieldType) {
+ if(is_object($obj->$fieldName)) {
+ $json .= $obj->$fieldName->toXML();
+ } else {
+ $json .= "<$fieldName>" . Convert::raw2xml($obj->$fieldName) . "$fieldName>\n";
+ }
+ }
+
+
+ foreach($obj->has_one() as $relName => $relClass) {
+ $fieldName = $relName . 'ID';
+ if($obj->$fieldName) {
+ $href = Director::absoluteURL(self::$api_base . "$relClass/" . $obj->$fieldName);
+ } else {
+ $href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
+ }
+ $json .= "<$relName linktype=\"has_one\" href=\"$href.xml\" id=\"{$obj->$fieldName}\" />\n";
+ }
+
+ foreach($obj->has_many() as $relName => $relClass) {
+ $json .= "<$relName linktype=\"has_many\" href=\"$objHref/$relName.xml\">\n";
+ $items = $obj->$relName();
+ foreach($items as $item) {
+ //$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
+ $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
+ $json .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\" />\n";
+ }
+ $json .= "$relName>\n";
+ }
+
+ foreach($obj->many_many() as $relName => $relClass) {
+ $json .= "<$relName linktype=\"many_many\" href=\"$objHref/$relName.xml\">\n";
+ $items = $obj->$relName();
+ foreach($items as $item) {
+ $href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
+ $json .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\" />\n";
+ }
+ $json .= "$relName>\n";
+ }
+
+ $json .= "$className>";
+
+ return $json;
+ }
+
+ /**
+ * Generate an XML representation of the given {@link DataObjectSet}.
+ *
+ * @param DataObjectSet $set
+ * @return String XML
+ */
+ public function convertDataObjectSet(DataObjectSet $set) {
+ Controller::curr()->getResponse()->addHeader("Content-type", "text/xml");
+ $className = $set->class;
+
+ $xml = "\n<$className>\n";
+ foreach($set as $item) {
+ if($item->canView()) $xml .= $this->convertDataObjectWithoutHeader($item);
+ }
+ $xml .= "$className>";
+
+ return $xml;
+ }
+}
\ No newline at end of file
diff --git a/cli-script.php b/cli-script.php
index 75daafcbf..0e4dffa1a 100755
--- a/cli-script.php
+++ b/cli-script.php
@@ -1,6 +1,10 @@
#!/usr/bin/php5
array, true);
+ }
+
}
?>
\ No newline at end of file
diff --git a/core/SSViewer.php b/core/SSViewer.php
index be5eda3fb..24fd40c87 100644
--- a/core/SSViewer.php
+++ b/core/SSViewer.php
@@ -109,6 +109,7 @@ class SSViewer extends Object {
*/
public function dontRewriteHashlinks() {
$this->rewriteHashlinks = false;
+ self::$options['rewriteHashlinks'] = false;
return $this;
}
diff --git a/core/control/Director.php b/core/control/Director.php
index 578273e3c..9689a64d3 100644
--- a/core/control/Director.php
+++ b/core/control/Director.php
@@ -577,7 +577,8 @@ class Director {
*/
static function set_environment_type($et) {
if($et != 'dev' && $et != 'test' && $et != 'live') {
- user_error("Director::set_environment_type passed '$et'. It should be passed dev, test, or live");
+ Debug::backtrace();
+ user_error("Director::set_environment_type passed '$et'. It should be passed dev, test, or live", E_USER_WARNING);
} else {
self::$environment_type = $et;
}
diff --git a/core/model/DataObject.php b/core/model/DataObject.php
index e09fa3e05..a58c8bf0d 100644
--- a/core/model/DataObject.php
+++ b/core/model/DataObject.php
@@ -1206,7 +1206,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
$model = singleton($component);
$records = DataObject::get($component);
$collect = ($model->hasMethod('customSelectOption')) ? 'customSelectOption' : current($model->summary_fields());
- $options = $records->filter_map('ID', $collect);
+ $options = $records ? $records->filter_map('ID', $collect) : array();
$fields->push(new DropdownField($relationship.'ID', $relationship, $options));
}
return $fields;
@@ -1216,9 +1216,21 @@ class DataObject extends ViewableData implements DataObjectInterface {
* Add the scaffold-generated relation fields to the given field set
*/
protected function addScaffoldRelationFields($fieldSet) {
- foreach($this->has_many() as $relationship => $component) {
- $relationshipFields = array_keys($this->searchable_fields());
- $fieldSet->push(new ComplexTableField($this, $relationship, $component, $relationshipFields));
+
+ 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;
}
@@ -1249,7 +1261,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
$fields = $this->scaffoldFormFields();
// If we don't have an ID, then relation fields don't work
if($this->ID) {
- $this->addScaffoldRelationFields($fields);
+ $fields = $this->addScaffoldRelationFields($fields);
}
return $fields;
}
@@ -2085,7 +2097,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
public function searchable_fields() {
$fields = $this->stat('searchable_fields');
if (!$fields) {
- $fields = array_fill_keys($this->summary_fields(), 'TextField');
+ $fields = array_fill_keys(array_keys($this->summary_fields()), 'TextField');
}
return $fields;
}
@@ -2101,11 +2113,19 @@ class DataObject extends ViewableData implements DataObjectInterface {
$fields = $this->stat('summary_fields');
if (!$fields) {
$fields = array();
- if ($this->hasField('Name')) $fields[] = 'Name';
- if ($this->hasField('Title')) $fields[] = 'Title';
- if ($this->hasField('Description')) $fields[] = 'Description';
- if ($this->hasField('Firstname')) $fields[] = 'Firstname';
+ if ($this->hasField('Name')) $fields['Name'] = 'Name';
+ if ($this->hasField('Title')) $fields['Title'] = 'Title';
+ if ($this->hasField('Description')) $fields['Description'] = 'Description';
+ if ($this->hasField('Firstname')) $fields['Firstname'] = 'Firstname';
}
+
+ // Final fail-over, just list all the fields :-S
+ if(!$fields) {
+ foreach(array_keys($this->db()) as $field) {
+ $fields[$field] = $field;
+ }
+ }
+
return $fields;
}
@@ -2128,7 +2148,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
} else {
if (is_array($type)) {
$filter = current($type);
- $filters[$name] = new $filter();
+ $filters[$name] = new $filter($name);
} else {
if (is_subclass_of($type, 'SearchFilter')) {
$filters[$name] = new $type($name);
diff --git a/core/model/DatabaseAdmin.php b/core/model/DatabaseAdmin.php
index 63e1a5751..cd964ad71 100644
--- a/core/model/DatabaseAdmin.php
+++ b/core/model/DatabaseAdmin.php
@@ -75,7 +75,7 @@ class DatabaseAdmin extends Controller {
* Updates the database schema, creating tables & fields as necessary.
*/
function build() {
- if(Director::isLive() && Security::database_is_ready() && (!Member::currentUser() || !Member::currentUser()->isAdmin())) {
+ if(Director::isLive() && Security::database_is_ready() && !Director::is_cli() && (!Member::currentUser() || !Member::currentUser()->isAdmin())) {
Security::permissionFailure($this,
"This page is secured and you need administrator rights to access it. " .
"Enter your credentials below and we will send you right along.");
diff --git a/core/model/SQLQuery.php b/core/model/SQLQuery.php
index 21053e44e..374a48db2 100755
--- a/core/model/SQLQuery.php
+++ b/core/model/SQLQuery.php
@@ -68,6 +68,7 @@ class SQLQuery extends Object {
/**
* Construct a new SQLQuery.
+ *
* @param array $select An array of fields to select.
* @param array $from An array of join clauses. The first one should be just the table name.
* @param array $where An array of filters, to be inserted into the WHERE clause.
@@ -76,7 +77,7 @@ class SQLQuery extends Object {
* @param array $having An array of having clauses.
* @param string $limit A LIMIT clause.
*/
- function __construct($select = array(), $from = array(), $where = "", $orderby = "", $groupby = "", $having = "", $limit = "") {
+ 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);
@@ -87,7 +88,76 @@ class SQLQuery extends Object {
parent::__construct();
}
-
+
+ /**
+ * Specify the list of columns to be selected by the query.
+ *
+ *
+ * // pass fields to select as single parameter array
+ * $query->select(array("Col1","Col2"))->from("MyTable");
+ *
+ * // pass fields to select as multiple parameters
+ * $query->select("Col1", "Col2")->from("MyTable");
+ *
+ *
+ * @param mixed $fields
+ * @return SQLQuery
+ */
+ public function select($fields) {
+ if (func_num_args() > 1) {
+ $this->select = func_get_args();
+ } else {
+ $this->select = is_array($fields) ? $fields : array($fields);
+ }
+ return $this;
+ }
+
+ /**
+ * Specify the target table to select from.
+ *
+ *
+ * $query->from("MyTable"); // SELECT * FROM MyTable
+ *
+ *
+ * @param string $table
+ * @return SQLQuery
+ */
+ public function from($table) {
+ $this->from[] = $table;
+ return $this;
+ }
+
+ /**
+ * Apply a predicate filter to the where clause.
+ *
+ * Accepts a variable length of arguments, which represent
+ * different ways of formatting a predicate in a where clause:
+ *
+ *
+ * // the entire predicate as a single string
+ * $query->where("Column = 'Value'");
+ *
+ * // an exact match predicate with a key value pair
+ * $query->where("Column", "Value");
+ *
+ * // a predicate with user defined operator
+ * $query->where("Column", "!=", "Value");
+ *
+ *
+ */
+ public function where() {
+ $args = func_get_args();
+ if (func_num_args() == 3) {
+ $filter = "{$args[0]} {$args[1]} '{$args[2]}'";
+ } elseif (func_num_args() == 2) {
+ $filter = "{$args[0]} = '{$args[1]}'";
+ } else {
+ $filter = $args[0];
+ }
+ $this->where[] = $filter;
+ return $this;
+ }
+
/**
* Use the disjunctive operator 'OR' to join filter expressions in the WHERE clause.
*/
@@ -147,19 +217,21 @@ class SQLQuery extends Object {
* @return string
*/
function getFilter() {
- return implode(") {$this->connective} (" , $this->where);
+ return ($this->where) ? implode(") {$this->connective} (" , $this->where) : '';
}
/**
* Generate the SQL statement for this query.
+ *
* @return string
*/
function sql() {
+ if (!$this->from) return '';
$distinct = $this->distinct ? "DISTINCT " : "";
- if($this->select) {
+ if($this->delete) {
+ $text = "DELETE ";
+ } else if($this->select) {
$text = "SELECT $distinct" . implode(", ", $this->select);
- } else {
- if($this->delete) $text = "DELETE ";
}
$text .= " FROM " . implode(" ", $this->from);
@@ -172,6 +244,15 @@ class SQLQuery extends Object {
return $text;
}
+ /**
+ * Return the generated SQL string for this query
+ *
+ * @return string
+ */
+ function __toString() {
+ return $this->sql();
+ }
+
/**
* Execute this query.
* @return Query
diff --git a/forms/ComplexTableField.php b/forms/ComplexTableField.php
index d80b7171f..5b931caf8 100755
--- a/forms/ComplexTableField.php
+++ b/forms/ComplexTableField.php
@@ -53,6 +53,9 @@ class ComplexTableField extends TableListField {
*/
protected $permissions = array(
"add",
+ "edit",
+ "show",
+ "delete",
//"export",
);
@@ -213,7 +216,6 @@ JS;
}
$this->sourceItems = DataObject::get($this->sourceClass, $this->sourceFilter, $sort, $this->sourceJoin, $limitClause);
-
$this->unpagedSourceItems = DataObject::get($this->sourceClass, $this->sourceFilter, $sort, $this->sourceJoin);
$this->totalCount = ($this->unpagedSourceItems) ? $this->unpagedSourceItems->TotalItems() : null;
@@ -436,8 +438,11 @@ JS;
// add relational fields
$detailFields->push(new HiddenField("ctf[parentClass]"," ",$this->getParentClass()));
- if( $this->relationAutoSetting )
- $detailFields->push(new HiddenField("$parentIdName"," ",$childData->ID));
+ if( $this->relationAutoSetting ) {
+ // Hack for model admin: model admin will have included a dropdown for the relation itself
+ $detailFields->removeByName($parentIdName);
+ $detailFields->push(new HiddenField("$parentIdName"," ",$this->sourceID()));
+ }
}
}
@@ -485,18 +490,17 @@ JS;
*
* @see {Form::ReferencedField}).
*/
- function saveComplexTableField($params) {
+ function saveComplexTableField($data, $form, $params) {
$className = $this->sourceClass();
$childData = new $className();
-
- $this->saveInto($childData);
+ $form->saveInto($childData);
$childData->write();
// if ajax-call in an iframe, update window
if(Director::is_ajax()) {
// Newly saved objects need their ID reflected in the reloaded form to avoid double saving
- $form = $this->controller->DetailForm();
- //$form->loadDataFrom($this->dataObject);
+ $childRequestHandler = new ComplexTableField_ItemRequest($this, $childData->ID);
+ $form = $childRequestHandler->DetailForm();
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
return FormResponse::respond();
} else {
@@ -542,18 +546,20 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
}
$this->methodName = "show";
- /*
- $this->sourceItems = $this->ctg->sourceItems();
-
- $this->pageSize = 1;
-
- if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
- $this->unpagedSourceItems->setPageLimits($_REQUEST['ctf'][$this->Name()]['start'], $this->pageSize, $this->totalCount);
- }
- */
-
echo $this->renderWith($this->ctf->templatePopup);
}
+
+ /**
+ * Returns a 1-element data object set that can be used for pagination.
+ */
+ /* this doesn't actually work :-(
+ function Paginator() {
+ $paginatingSet = new DataObjectSet(array($this->dataObj()));
+ $start = isset($_REQUEST['ctf']['start']) ? $_REQUEST['ctf']['start'] : 0;
+ $paginatingSet->setPageLimits($start, 1, $this->ctf->TotalCount());
+ return $paginatingSet;
+ }
+ */
/**
* Just a hook, processed in {DetailForm()}
@@ -566,25 +572,23 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
}
$this->methodName = "edit";
- /*
- $this->sourceItems = $this->sourceItems();
-
- $this->pageSize = 1;
-
- if(is_numeric($_REQUEST['ctf']['start'])) {
- $this->unpagedSourceItems->setPageLimits($_REQUEST['ctf']['start'], $this->pageSize, $this->totalCount);
- }
- */
-
echo $this->renderWith($this->ctf->templatePopup);
}
+ function delete() {
+ if($this->ctf->Can('delete') !== true) {
+ return false;
+ }
+
+ $this->dataObj()->delete();
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Return the data object being manipulated
*/
- function obj() {
+ function dataObj() {
// used to discover fields if requested and for population of field
if(is_numeric($this->itemID)) {
// we have to use the basedataclass, otherwise we might exclude other subclasses
@@ -605,7 +609,7 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
* @param int $childID
*/
function DetailForm($childID = null) {
- $childData = $this->obj();
+ $childData = $this->dataObj();
$fields = $this->ctf->getFieldsFor($childData);
$validator = $this->ctf->getValidatorFor($childData);
@@ -631,13 +635,13 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
* @see {Form::ReferencedField}).
*/
function saveComplexTableField($data, $form, $request) {
- $form->saveInto($this->obj());
- $this->obj()->write();
+ $form->saveInto($this->dataObj());
+ $this->dataObj()->write();
// if ajax-call in an iframe, update window
if(Director::is_ajax()) {
// Newly saved objects need their ID reflected in the reloaded form to avoid double saving
- $form = $this->controller->DetailForm();
+ $form = $this->DetailForm();
//$form->loadDataFrom($this->dataObject);
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
return FormResponse::respond();
@@ -647,58 +651,52 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
}
}
- function PopupBaseLink() {
- $link = $this->FormAction() . "&action_callfieldmethod&fieldName={$this->Name()}";
- if(!strpos($link,'ctf[ID]')) {
- $link = str_replace('&','&',HTTP::setGetVar('ctf[ID]',$this->sourceID(),$link));
- }
- return $link;
- }
-
function PopupCurrentItem() {
return $_REQUEST['ctf']['start']+1;
}
-
+
function PopupFirstLink() {
- if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
+ $this->ctf->LinkToItem();
+
+ if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
return null;
}
$item = $this->unpagedSourceItems->First();
$start = 0;
- return Convert::raw2att($this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}");
+ return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
}
function PopupLastLink() {
- if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
+ if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
return null;
}
$item = $this->unpagedSourceItems->Last();
$start = $this->totalCount - 1;
- return Convert::raw2att($this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}");
+ return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
}
function PopupNextLink() {
- if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
+ if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
return null;
}
$item = $this->unpagedSourceItems->getIterator()->getOffset($_REQUEST['ctf']['start'] + 1);
$start = $_REQUEST['ctf']['start'] + 1;
- return Convert::raw2att($this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}");
+ return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
}
function PopupPrevLink() {
- if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
+ if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
return null;
}
$item = $this->unpagedSourceItems->getIterator()->getOffset($_REQUEST['ctf']['start'] - 1);
$start = $_REQUEST['ctf']['start'] - 1;
- return Convert::raw2att($this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}");
+ return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
}
/**
@@ -722,7 +720,7 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->totalCount;$i++) {
$start = $i - 1;
$item = $this->unpagedSourceItems->getIterator()->getOffset($i-1);
- $links['link'] = Convert::raw2att($this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}");
+ $links['link'] = Controller::join_links($this->Link() . "$this->methodName?ctf[start]={$start}");
$links['number'] = $i;
$links['active'] = $i == $currentItem ? false : true;
$result->push(new ArrayData($links));
@@ -730,6 +728,9 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
return $result;
}
+ function ShowPagination() {
+ return false;
+ }
/**
@@ -886,10 +887,6 @@ class ComplexTableField_Popup extends Form {
function FieldHolder() {
return $this->renderWith('ComplexTableField_Form');
}
-
- function ShowPagination() {
- return $this->controller->ShowPagination();
- }
}
?>
diff --git a/forms/FieldSet.php b/forms/FieldSet.php
index e39b73552..d5ce93e84 100755
--- a/forms/FieldSet.php
+++ b/forms/FieldSet.php
@@ -32,6 +32,7 @@ class FieldSet extends DataObjectSet {
if($field->hasData()) {
$name = $field->Name();
if(isset($list[$name])) {
+ $errSuffix = "";
if($this->form) $errSuffix = " in your '{$this->form->class}' form called '" . $this->form->Name() . "'";
else $errSuffix = '';
user_error("collateDataFields() I noticed that a field called '$name' appears twice$errSuffix.", E_USER_ERROR);
diff --git a/forms/Form.php b/forms/Form.php
index 6e907514f..60b9c44ce 100644
--- a/forms/Form.php
+++ b/forms/Form.php
@@ -813,6 +813,15 @@ class Form extends RequestHandlingData {
));
}
+ /**
+ * Return a rendered version of this form, suitable for ajax post-back.
+ * It triggers slightly different behaviour, such as disabling the rewriting of # links
+ */
+ function forAjaxTemplate() {
+ $view = new SSViewer("Form");
+ return $view->dontRewriteHashlinks()->process($this);
+ }
+
/**
* Returns an HTML rendition of this form, without the