mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00: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@60204 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
e25f44604f
commit
9ac464cc57
@ -81,7 +81,7 @@ Director::addRules(10, array(
|
|||||||
'images/$Action/$Class/$ID/$Field' => 'Image_Uploader',
|
'images/$Action/$Class/$ID/$Field' => 'Image_Uploader',
|
||||||
'' => 'RootURLController',
|
'' => 'RootURLController',
|
||||||
'sitemap.xml' => 'GoogleSitemap',
|
'sitemap.xml' => 'GoogleSitemap',
|
||||||
'api/v1/$ClassName/$ID' => 'RestfulServer',
|
'api/v1/$ClassName/$ID/$Relation' => 'RestfulServer',
|
||||||
'dev/$Action/$NestedAction' => 'DevelopmentAdmin'
|
'dev/$Action/$NestedAction' => 'DevelopmentAdmin'
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -42,16 +42,33 @@ class RestfulServer extends Controller {
|
|||||||
if(!isset($this->urlParams['ClassName'])) return $this->notFound();
|
if(!isset($this->urlParams['ClassName'])) return $this->notFound();
|
||||||
$className = $this->urlParams['ClassName'];
|
$className = $this->urlParams['ClassName'];
|
||||||
$id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : null;
|
$id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : null;
|
||||||
|
$relation = (isset($this->urlParams['Relation'])) ? $this->urlParams['Relation'] : null;
|
||||||
|
|
||||||
|
// This is a little clumsy and should be improved with the new TokenisedURL that's coming
|
||||||
|
if(strpos($relation,'.') !== false) list($relation, $extension) = explode('.', $relation, 2);
|
||||||
|
else if(strpos($id,'.') !== false) list($id, $extension) = explode('.', $id, 2);
|
||||||
|
else if(strpos($className,'.') !== false) list($className, $extension) = explode('.', $className, 2);
|
||||||
|
else $extension = null;
|
||||||
|
|
||||||
|
// Determine mime-type from extension
|
||||||
|
$contentMap = array(
|
||||||
|
'xml' => 'text/xml',
|
||||||
|
'json' => 'text/json',
|
||||||
|
'js' => 'text/json',
|
||||||
|
'xhtml' => 'text/html',
|
||||||
|
'html' => 'text/html',
|
||||||
|
);
|
||||||
|
$contentType = isset($contentMap[$extension]) ? $contentMap[$extension] : 'text/xml';
|
||||||
|
|
||||||
switch($requestMethod) {
|
switch($requestMethod) {
|
||||||
case 'GET':
|
case 'GET':
|
||||||
return $this->getHandler($className, $id);
|
return $this->getHandler($className, $id, $relation, $contentType);
|
||||||
|
|
||||||
case 'PUT':
|
case 'PUT':
|
||||||
return $this->putHandler($className, $id);
|
return $this->putHandler($className, $id, $relation, $contentType);
|
||||||
|
|
||||||
case 'DELETE':
|
case 'DELETE':
|
||||||
return $this->deleteHandler($className, $id);
|
return $this->deleteHandler($className, $id, $relation, $contentType);
|
||||||
|
|
||||||
case 'POST':
|
case 'POST':
|
||||||
}
|
}
|
||||||
@ -83,46 +100,67 @@ class RestfulServer extends Controller {
|
|||||||
* - static $api_access must be set. This enables the API on a class by class basis
|
* - static $api_access must be set. This enables the API on a class by class basis
|
||||||
* - $obj->canView() must return true. This lets you implement record-level security
|
* - $obj->canView() must return true. This lets you implement record-level security
|
||||||
*/
|
*/
|
||||||
protected function getHandler($className, $id) {
|
protected function getHandler($className, $id, $relation, $contentType) {
|
||||||
$obj = DataObject::get_by_id($className, $id);
|
if($id) {
|
||||||
if(!$obj) {
|
$obj = DataObject::get_by_id($className, $id);
|
||||||
return $this->notFound();
|
if(!$obj) {
|
||||||
}
|
return $this->notFound();
|
||||||
|
|
||||||
// TO DO - inspect that Accept header as well. $_GET['accept'] can still be checked, as it's handy for debugging
|
|
||||||
$contentType = isset($_GET['accept']) ? $_GET['accept'] : 'text/xml';
|
|
||||||
|
|
||||||
if($obj->stat('api_access') && $obj->canView()) {
|
|
||||||
switch($contentType) {
|
|
||||||
case "text/xml":
|
|
||||||
$this->getResponse()->addHeader("Content-type", "text/xml");
|
|
||||||
return $this->dataObjectAsXML($obj);
|
|
||||||
|
|
||||||
case "text/json":
|
|
||||||
$this->getResponse()->addHeader("Content-type", "text/json");
|
|
||||||
return $this->dataObjectAsJSON($obj);
|
|
||||||
|
|
||||||
case "text/html":
|
|
||||||
case "application/xhtml+xml":
|
|
||||||
$this->getResponse()->addHeader("Content-type", "text/json");
|
|
||||||
return $this->dataObjectAsXHTML($obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!$obj->stat('api_access') || !$obj->canView()) {
|
||||||
|
return $this->permissionFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($relation) {
|
||||||
|
if($obj->hasMethod($relation)) $obj = $obj->$relation();
|
||||||
|
else return $this->notFound();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return $this->permissionFailure();
|
$obj = DataObject::get($className, "");
|
||||||
|
if(!singleton($className)->stat('api_access')) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate an XML representation of the given DataObject.
|
* Generate an XML representation of the given DataObject.
|
||||||
*/
|
*/
|
||||||
protected function dataObjectAsXML(DataObject $obj) {
|
protected function dataObjectAsXML(DataObject $obj, $includeHeader = true) {
|
||||||
$className = $obj->class;
|
$className = $obj->class;
|
||||||
$id = $obj->ID;
|
$id = $obj->ID;
|
||||||
|
$objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID");
|
||||||
|
|
||||||
$json = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<$className>\n";
|
$json = "";
|
||||||
|
if($includeHeader) $json .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||||
|
$json .= "<$className href=\"$objHref.xml\">\n";
|
||||||
foreach($obj->db() as $fieldName => $fieldType) {
|
foreach($obj->db() as $fieldName => $fieldType) {
|
||||||
$json .= "<$fieldName>" . Convert::raw2xml($obj->$fieldName) . "</$fieldName>\n";
|
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) {
|
foreach($obj->has_one() as $relName => $relClass) {
|
||||||
$fieldName = $relName . 'ID';
|
$fieldName = $relName . 'ID';
|
||||||
@ -131,27 +169,26 @@ class RestfulServer extends Controller {
|
|||||||
} else {
|
} else {
|
||||||
$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
|
$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
|
||||||
}
|
}
|
||||||
$json .= "<$relName linktype=\"has_one\" href=\"$href\" id=\"{$obj->$fieldName}\" />\n";
|
$json .= "<$relName linktype=\"has_one\" href=\"$href.xml\" id=\"{$obj->$fieldName}\" />\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($obj->has_many() as $relName => $relClass) {
|
foreach($obj->has_many() as $relName => $relClass) {
|
||||||
$json .= "<$relName linktype=\"has_many\">\n";
|
$json .= "<$relName linktype=\"has_many\" href=\"$objHref/$relName.xml\">\n";
|
||||||
$items = $obj->$relName();
|
$items = $obj->$relName();
|
||||||
foreach($items as $item) {
|
foreach($items as $item) {
|
||||||
//$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
|
//$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
|
||||||
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
||||||
$json .= "<$relClass href=\"$href\" id=\"{$item->ID}\" />\n";
|
$json .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\" />\n";
|
||||||
}
|
}
|
||||||
$json .= "</$relName>\n";
|
$json .= "</$relName>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($obj->many_many() as $relName => $relClass) {
|
foreach($obj->many_many() as $relName => $relClass) {
|
||||||
$json .= "<$relName linktype=\"many_many\">\n";
|
$json .= "<$relName linktype=\"many_many\" href=\"$objHref/$relName.xml\">\n";
|
||||||
$items = $obj->$relName();
|
$items = $obj->$relName();
|
||||||
foreach($items as $item) {
|
foreach($items as $item) {
|
||||||
//$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
|
|
||||||
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
||||||
$json .= "<$relClass href=\"$href\" id=\"{$item->ID}\" />\n";
|
$json .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\" />\n";
|
||||||
}
|
}
|
||||||
$json .= "</$relName>\n";
|
$json .= "</$relName>\n";
|
||||||
}
|
}
|
||||||
@ -161,6 +198,20 @@ class RestfulServer extends Controller {
|
|||||||
return $json;
|
return $json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an XML representation of the given DataObject.
|
||||||
|
*/
|
||||||
|
protected function dataObjectSetAsXML(DataObjectSet $set) {
|
||||||
|
$className = $set->class;
|
||||||
|
|
||||||
|
$json = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<$className>\n";
|
||||||
|
foreach($set as $item) {
|
||||||
|
if($item->canView()) $json .= $this->dataObjectAsXML($item, false);
|
||||||
|
}
|
||||||
|
$json .= "</$className>";
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate an XML representation of the given DataObject.
|
* Generate an XML representation of the given DataObject.
|
||||||
@ -171,7 +222,11 @@ class RestfulServer extends Controller {
|
|||||||
|
|
||||||
$json = "{\n className : \"$className\",\n";
|
$json = "{\n className : \"$className\",\n";
|
||||||
foreach($obj->db() as $fieldName => $fieldType) {
|
foreach($obj->db() as $fieldName => $fieldType) {
|
||||||
$jsonParts[] = "$fieldName : \"" . Convert::raw2js($obj->$fieldName) . "\"";
|
if(is_object($obj->$fieldName)) {
|
||||||
|
$jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON();
|
||||||
|
} else {
|
||||||
|
$jsonParts[] = "$fieldName : \"" . Convert::raw2js($obj->$fieldName) . "\"";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($obj->has_one() as $relName => $relClass) {
|
foreach($obj->has_one() as $relName => $relClass) {
|
||||||
@ -181,7 +236,7 @@ class RestfulServer extends Controller {
|
|||||||
} else {
|
} else {
|
||||||
$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
|
$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName");
|
||||||
}
|
}
|
||||||
$jsonParts[] = "$relName : { className : \"$relClass\", href : \"$href\", id : \"{$obj->$fieldName}\" }";
|
$jsonParts[] = "$relName : { className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($obj->has_many() as $relName => $relClass) {
|
foreach($obj->has_many() as $relName => $relClass) {
|
||||||
@ -190,7 +245,7 @@ class RestfulServer extends Controller {
|
|||||||
foreach($items as $item) {
|
foreach($items as $item) {
|
||||||
//$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
|
//$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
|
||||||
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
||||||
$jsonInnerParts[] = "{ className : \"$relClass\", href : \"$href\", id : \"{$obj->$fieldName}\" }";
|
$jsonInnerParts[] = "{ className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
|
||||||
}
|
}
|
||||||
$jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . " \n ]";
|
$jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . " \n ]";
|
||||||
}
|
}
|
||||||
@ -201,13 +256,26 @@ class RestfulServer extends Controller {
|
|||||||
foreach($items as $item) {
|
foreach($items as $item) {
|
||||||
//$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
|
//$href = Director::absoluteURL(self::$api_base . "$className/$id/$relName/$item->ID");
|
||||||
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
$href = Director::absoluteURL(self::$api_base . "$relClass/$item->ID");
|
||||||
$jsonInnerParts[] = " { className : \"$relClass\", href : \"$href\", id : \"{$obj->$fieldName}\" }";
|
$jsonInnerParts[] = " { className : \"$relClass\", href : \"$href.json\", id : \"{$obj->$fieldName}\" }";
|
||||||
}
|
}
|
||||||
$jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . "\n ]";
|
$jsonParts[] = "$relName : [\n " . implode(",\n ", $jsonInnerParts) . "\n ]";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "{\n " . implode(",\n ", $jsonParts) . "\n}";
|
return "{\n " . implode(",\n ", $jsonParts) . "\n}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an XML representation of the given DataObject.
|
||||||
|
*/
|
||||||
|
protected 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
|
* Handler for object delete
|
||||||
*/
|
*/
|
||||||
|
@ -145,5 +145,13 @@ class ClassInfo {
|
|||||||
global $_ALL_CLASSES;
|
global $_ALL_CLASSES;
|
||||||
return (isset($_ALL_CLASSES['implementors'][$interfaceName])) ? $_ALL_CLASSES['implementors'][$interfaceName] : false;
|
return (isset($_ALL_CLASSES['implementors'][$interfaceName])) ? $_ALL_CLASSES['implementors'][$interfaceName] : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given class implements the given interface
|
||||||
|
*/
|
||||||
|
static function classImplements($className, $interfaceName) {
|
||||||
|
global $_ALL_CLASSES;
|
||||||
|
return isset($_ALL_CLASSES['implementors'][$interfaceName]) ? in_array($className, $_ALL_CLASSES['implementors'][$interfaceName]) : false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
@ -557,6 +557,14 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
|
|
||||||
if(($this->ID && is_numeric($this->ID)) && !$forceInsert) {
|
if(($this->ID && is_numeric($this->ID)) && !$forceInsert) {
|
||||||
$dbCommand = 'update';
|
$dbCommand = 'update';
|
||||||
|
|
||||||
|
// Update the changed array with references to changed obj-fields
|
||||||
|
foreach($this->record as $k => $v) {
|
||||||
|
if(is_object($v) && $v->isChanged()) {
|
||||||
|
$this->changed[$k] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else{
|
} else{
|
||||||
$dbCommand = 'insert';
|
$dbCommand = 'insert';
|
||||||
|
|
||||||
@ -1022,7 +1030,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
*
|
*
|
||||||
* @return array The database fields
|
* @return array The database fields
|
||||||
*/
|
*/
|
||||||
public function db() {
|
public function db($component = null) {
|
||||||
$classes = ClassInfo::ancestry($this);
|
$classes = ClassInfo::ancestry($this);
|
||||||
$good = false;
|
$good = false;
|
||||||
$items = array();
|
$items = array();
|
||||||
@ -1035,7 +1043,15 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
eval("\$items = array_merge((array){$class}::\$db, (array)\$items);");
|
|
||||||
|
if($component) {
|
||||||
|
$candidate = eval("return isset({$class}::\$db[\$component]) ? {$class}::\$db[\$component] : null;");
|
||||||
|
if($candidate) {
|
||||||
|
return $candidate;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eval("\$items = array_merge((array){$class}::\$db, (array)\$items);");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $items;
|
return $items;
|
||||||
@ -1176,8 +1192,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
public function scaffoldFormFields() {
|
public function scaffoldFormFields() {
|
||||||
$fields = new FieldSet();
|
$fields = new FieldSet();
|
||||||
|
|
||||||
foreach($this->inheritedDatabaseFields() as $fieldName => $fieldType) {
|
foreach($this->db() as $fieldName => $fieldType) {
|
||||||
|
|
||||||
// @todo Pass localized title
|
// @todo Pass localized title
|
||||||
// commented out, to be less of a pain in the ass
|
// commented out, to be less of a pain in the ass
|
||||||
//$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField());
|
//$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField());
|
||||||
@ -1243,6 +1258,22 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
* @return mixed The field value
|
* @return mixed The field value
|
||||||
*/
|
*/
|
||||||
protected function getField($field) {
|
protected function getField($field) {
|
||||||
|
// If we already have an object in $this->record, then we should just return that
|
||||||
|
if(isset($this->record[$field]) && is_object($this->record[$field])) return $this->record[$field];
|
||||||
|
|
||||||
|
// Otherwise, we need to determine if this is a complex field
|
||||||
|
$fieldClass = $this->db($field);
|
||||||
|
if($fieldClass && ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
|
||||||
|
$helperPair = $this->castingHelperPair($field);
|
||||||
|
$constructor = $helperPair['castingHelper'];
|
||||||
|
$fieldName = $field;
|
||||||
|
$fieldObj = eval($constructor);
|
||||||
|
if(isset($this->record[$field])) $fieldObj->setValue($this->record[$field], $this->record);
|
||||||
|
$this->record[$field] = $fieldObj;
|
||||||
|
|
||||||
|
return $this->record[$field];
|
||||||
|
}
|
||||||
|
|
||||||
return isset($this->record[$field]) ? $this->record[$field] : null;
|
return isset($this->record[$field]) ? $this->record[$field] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1293,28 +1324,36 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
* @param mixed $val New field value
|
* @param mixed $val New field value
|
||||||
*/
|
*/
|
||||||
function setField($fieldName, $val) {
|
function setField($fieldName, $val) {
|
||||||
$defaults = $this->stat('defaults');
|
// Situation 1: Passing a DBField
|
||||||
// if a field is not existing or has strictly changed
|
if($val instanceof DBField) {
|
||||||
if(!isset($this->record[$fieldName]) || $this->record[$fieldName] !== $val) {
|
$val->Name = $fieldName;
|
||||||
// TODO Add check for php-level defaults which are not set in the db
|
|
||||||
// TODO Add check for hidden input-fields (readonly) which are not set in the db
|
|
||||||
if(
|
|
||||||
// Only existing fields
|
|
||||||
$this->fieldExists($fieldName)
|
|
||||||
// Catches "0"==NULL
|
|
||||||
&& (isset($this->record[$fieldName]) && (intval($val) != intval($this->record[$fieldName])))
|
|
||||||
// Main non type-based check
|
|
||||||
&& (isset($this->record[$fieldName]) && $this->record[$fieldName] != $val)
|
|
||||||
) {
|
|
||||||
// Non-strict check fails, so value really changed, e.g. "abc" != "cde"
|
|
||||||
$this->changed[$fieldName] = 2;
|
|
||||||
} else {
|
|
||||||
// Record change-level 1 if only the type changed, e.g. 0 !== NULL
|
|
||||||
$this->changed[$fieldName] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is always saved back when strict check succeeds
|
|
||||||
$this->record[$fieldName] = $val;
|
$this->record[$fieldName] = $val;
|
||||||
|
|
||||||
|
// Situation 2: Passing a literal
|
||||||
|
} else {
|
||||||
|
$defaults = $this->stat('defaults');
|
||||||
|
// if a field is not existing or has strictly changed
|
||||||
|
if(!isset($this->record[$fieldName]) || $this->record[$fieldName] !== $val) {
|
||||||
|
// TODO Add check for php-level defaults which are not set in the db
|
||||||
|
// TODO Add check for hidden input-fields (readonly) which are not set in the db
|
||||||
|
if(
|
||||||
|
// Only existing fields
|
||||||
|
$this->fieldExists($fieldName)
|
||||||
|
// Catches "0"==NULL
|
||||||
|
&& (isset($this->record[$fieldName]) && (intval($val) != intval($this->record[$fieldName])))
|
||||||
|
// Main non type-based check
|
||||||
|
&& (isset($this->record[$fieldName]) && $this->record[$fieldName] != $val)
|
||||||
|
) {
|
||||||
|
// Non-strict check fails, so value really changed, e.g. "abc" != "cde"
|
||||||
|
$this->changed[$fieldName] = 2;
|
||||||
|
} else {
|
||||||
|
// Record change-level 1 if only the type changed, e.g. 0 !== NULL
|
||||||
|
$this->changed[$fieldName] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// value is always saved back when strict check succeeds
|
||||||
|
$this->record[$fieldName] = $val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1334,7 +1373,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
$castingHelper = $this->castingHelper($fieldName);
|
$castingHelper = $this->castingHelper($fieldName);
|
||||||
if($castingHelper) {
|
if($castingHelper) {
|
||||||
$fieldObj = eval($castingHelper);
|
$fieldObj = eval($castingHelper);
|
||||||
$fieldObj->setVal($val);
|
$fieldObj->setValue($val);
|
||||||
$fieldObj->saveInto($this);
|
$fieldObj->saveInto($this);
|
||||||
} else {
|
} else {
|
||||||
$this->$fieldName = $val;
|
$this->$fieldName = $val;
|
||||||
@ -1349,7 +1388,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
* @return boolean True if the given field exists
|
* @return boolean True if the given field exists
|
||||||
*/
|
*/
|
||||||
public function hasField($field) {
|
public function hasField($field) {
|
||||||
return array_key_exists($field, $this->record);
|
return array_key_exists($field, $this->record) || $this->fieldExists($field);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1486,14 +1525,17 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
* @return DBField The field as a DBField object
|
* @return DBField The field as a DBField object
|
||||||
*/
|
*/
|
||||||
public function dbObject($fieldName) {
|
public function dbObject($fieldName) {
|
||||||
|
return $this->obj($fieldName);
|
||||||
|
/*
|
||||||
$helperPair = $this->castingHelperPair($fieldName);
|
$helperPair = $this->castingHelperPair($fieldName);
|
||||||
$constructor = $helperPair['castingHelper'];
|
$constructor = $helperPair['castingHelper'];
|
||||||
|
|
||||||
if($obj = eval($constructor)) {
|
if($obj = eval($constructor)) {
|
||||||
$obj->setVal($this->$fieldName, $this->record);
|
$obj->setValue($this->$fieldName, $this->record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $obj;
|
return $obj;
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1560,18 +1602,34 @@ class DataObject extends ViewableData implements DataObjectInterface {
|
|||||||
// Build our intial query
|
// Build our intial query
|
||||||
$query = new SQLQuery($select, "`$baseClass`", $filter, $sort);
|
$query = new SQLQuery($select, "`$baseClass`", $filter, $sort);
|
||||||
|
|
||||||
|
// Add SQL for multi-value fields on the base table
|
||||||
|
$databaseFields = $this->databaseFields();
|
||||||
|
if($databaseFields) foreach($databaseFields as $k => $v) {
|
||||||
|
if(!in_array($k, array('ClassName', 'LastEdited', 'Created'))) {
|
||||||
|
if(ClassInfo::classImplements($v, 'CompositeDBField')) {
|
||||||
|
$this->obj($k)->addToQuery($query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Join all the tables
|
// Join all the tables
|
||||||
if($tableClasses) {
|
if($tableClasses) {
|
||||||
foreach($tableClasses as $tableClass) {
|
foreach($tableClasses as $tableClass) {
|
||||||
$query->from[$tableClass] = "LEFT JOIN `$tableClass` ON `$tableClass`.ID = `$baseClass`.ID";
|
$query->from[$tableClass] = "LEFT JOIN `$tableClass` ON `$tableClass`.ID = `$baseClass`.ID";
|
||||||
$query->select[] = "`$tableClass`.*";
|
$query->select[] = "`$tableClass`.*";
|
||||||
// ask each $db field on the specific table for alterations to the query
|
|
||||||
$uninheritedDbFields = singleton($tableClass)->uninherited('db',true);
|
// Add SQL for multi-value fields
|
||||||
if($uninheritedDbFields) foreach($uninheritedDbFields as $fieldName => $fieldType) {
|
$SNG = singleton($tableClass);
|
||||||
singleton($tableClass)->obj($fieldName)->addToQuery($query);
|
foreach($SNG->databaseFields() as $k => $v) {
|
||||||
|
if(!in_array($k, array('ClassName', 'LastEdited', 'Created'))) {
|
||||||
|
if(ClassInfo::classImplements($v, 'CompositeDBField')) {
|
||||||
|
$SNG->obj($k)->addToQuery($query);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$query->select[] = "`$baseClass`.ID";
|
$query->select[] = "`$baseClass`.ID";
|
||||||
$query->select[] = "if(`$baseClass`.ClassName,`$baseClass`.ClassName,'$baseClass') AS RecordClassName";
|
$query->select[] = "if(`$baseClass`.ClassName,`$baseClass`.ClassName,'$baseClass') AS RecordClassName";
|
||||||
|
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Single field in the database.
|
* Single field in the database.
|
||||||
* Every field from the database is represented as a sub-class of DBField. In addition to supporting
|
* Every field from the database is represented as a sub-class of DBField.
|
||||||
* the creation of the field in the database,
|
*
|
||||||
|
* <h2>Multi-value DBField objects</h2>
|
||||||
|
* Sometimes you will want to make DBField classes that don't have a 1-1 match to database fields. To do this, there are a
|
||||||
|
* number of fields for you to overload.
|
||||||
|
* - Overload {@link writeToManipulation} to add the appropriate references to the INSERT or UPDATE command
|
||||||
|
* - Overload {@link addToQuery} to add the appropriate items to a SELECT query's field list
|
||||||
|
* - Add appropriate accessor methods
|
||||||
*
|
*
|
||||||
* @package sapphire
|
* @package sapphire
|
||||||
* @subpackage model
|
* @subpackage model
|
||||||
@ -38,8 +44,30 @@ abstract class DBField extends ViewableData {
|
|||||||
return $dbField;
|
return $dbField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
function setVal($value, $record = null) {
|
function setVal($value, $record = null) {
|
||||||
return $this->setValue($value);
|
return $this->setValue($value, $record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name of this field.
|
||||||
|
* The name should never be altered, but it if was never given a name in the first place you can set a name.
|
||||||
|
* If you try an alter the name a warning will be thrown.
|
||||||
|
*/
|
||||||
|
function setName($name) {
|
||||||
|
if($this->name) {
|
||||||
|
user_error("DBField::setName() shouldn't be called once a DBField already has a name. It's partially immutable - it shouldn't be altered after it's given a value.", E_USER_WARNING);
|
||||||
|
}
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this field
|
||||||
|
*/
|
||||||
|
function getName() {
|
||||||
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +120,9 @@ abstract class DBField extends ViewableData {
|
|||||||
*
|
*
|
||||||
* @param Query $query
|
* @param Query $query
|
||||||
*/
|
*/
|
||||||
function addToQuery(&$query) {}
|
function addToQuery(&$query) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function setTable($tableName) {
|
function setTable($tableName) {
|
||||||
$this->tableName = $tableName;
|
$this->tableName = $tableName;
|
||||||
|
@ -91,7 +91,6 @@ class SearchContext extends Object {
|
|||||||
public function getQuery($searchParams) {
|
public function getQuery($searchParams) {
|
||||||
$q = new SQLQuery("*", $this->modelClass);
|
$q = new SQLQuery("*", $this->modelClass);
|
||||||
$this->processFilters($q);
|
$this->processFilters($q);
|
||||||
|
|
||||||
return $q;
|
return $q;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +161,8 @@ class SearchContext extends Object {
|
|||||||
$fields = array_filter($fields, array($this,'clearEmptySearchFields'));
|
$fields = array_filter($fields, array($this,'clearEmptySearchFields'));
|
||||||
$length = count($fields);
|
$length = count($fields);
|
||||||
foreach($fields as $key=>$val) {
|
foreach($fields as $key=>$val) {
|
||||||
if ($val != '') {
|
// Array values come from more complex fields - for now let's just disable searching on them
|
||||||
|
if (!is_array($val) && $val != '') {
|
||||||
$filter .= "`$key`='$val'";
|
$filter .= "`$key`='$val'";
|
||||||
} else {
|
} else {
|
||||||
$length--;
|
$length--;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user