(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@60234 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-08-09 06:53:26 +00:00
parent 7d2415c656
commit c1440e0b02
11 changed files with 168 additions and 77 deletions

View File

@ -23,10 +23,11 @@ class JSONDataFormatter extends DataFormatter {
$json = "{\n className : \"$className\",\n"; $json = "{\n className : \"$className\",\n";
foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) { foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
if(is_subclass_of($obj->$fieldName, 'Object') && $obj->hasMethod('toJSON')) { $fieldValue = $obj->$fieldName;
$jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON(); if(is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toJSON')) {
$jsonParts[] = "$fieldName : " . $fieldValue->toJSON();
} else { } else {
$jsonParts[] = "$fieldName : \"" . Convert::raw2json($obj->$fieldName) . "\""; $jsonParts[] = "$fieldName : " . Convert::raw2json($fieldValue);
} }
} }

View File

@ -46,6 +46,34 @@ class RestfulServer extends Controller {
protected static $api_base = "api/v1/"; protected static $api_base = "api/v1/";
/**
* If no extension is given in the request, resolve to this extension
* (and subsequently the {@link self::$default_mimetype}.
*
* @var string
*/
protected static $default_extension = "xml";
/**
* If no extension is given, resolve the request to this mimetype.
*
* @var string
*/
protected static $default_mimetype = "text/xml";
/**
* Maps common extensions to their mimetype representations.
*
* @var array
*/
protected static $mimetype_map = array(
'xml' => 'text/xml',
'json' => 'text/json',
'js' => 'text/json',
'xhtml' => 'text/html',
'html' => 'text/html',
);
/* /*
function handleItem($request) { function handleItem($request) {
return new RestfulServer_Item(DataObject::get_by_id($request->param("ClassName"), $request->param("ID"))); return new RestfulServer_Item(DataObject::get_by_id($request->param("ClassName"), $request->param("ID")));
@ -62,39 +90,22 @@ class RestfulServer extends Controller {
*/ */
function index() { function index() {
ContentNegotiator::disable(); ContentNegotiator::disable();
$requestMethod = $_SERVER['REQUEST_METHOD']; $requestMethod = $_SERVER['REQUEST_METHOD'];
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; $relation = (isset($this->urlParams['Relation'])) ? $this->urlParams['Relation'] : null;
$extension = $this->request->getExtension();
// 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';
if(!$extension) $extension = "xml";
$formatter = DataFormatter::for_extension($extension); //$this->dataFormatterFromMime($contentType);
if($customFields = $this->request->getVar('fields')) $formatter->setCustomFields(explode(',',$customFields));
$relationDepth = $this->request->getVar('relationdepth');
if(is_numeric($relationDepth)) $formatter->relationDepth = (int)$relationDepth;
switch($requestMethod) { switch($requestMethod) {
case 'GET': case 'GET':
return $this->getHandler($className, $id, $relation, $formatter); return $this->getHandler($className, $id, $relation);
case 'PUT': case 'PUT':
return $this->putHandler($className, $id, $relation, $formatter); return $this->putHandler($className, $id, $relation);
case 'DELETE': case 'DELETE':
return $this->deleteHandler($className, $id, $relation, $formatter); return $this->deleteHandler($className, $id, $relation);
case 'POST': case 'POST':
} }
@ -129,10 +140,9 @@ class RestfulServer extends Controller {
* @param String $className * @param String $className
* @param Int $id * @param Int $id
* @param String $relation * @param String $relation
* @param String $contentType
* @return String The serialized representation of the requested object(s) - usually XML or JSON. * @return String The serialized representation of the requested object(s) - usually XML or JSON.
*/ */
protected function getHandler($className, $id, $relation, $formatter) { protected function getHandler($className, $id, $relation) {
$sort = array( $sort = array(
'sort' => $this->request->getVar('sort'), 'sort' => $this->request->getVar('sort'),
'dir' => $this->request->getVar('dir') 'dir' => $this->request->getVar('dir')
@ -141,6 +151,8 @@ class RestfulServer extends Controller {
'start' => $this->request->getVar('start'), 'start' => $this->request->getVar('start'),
'limit' => $this->request->getVar('limit') 'limit' => $this->request->getVar('limit')
); );
$formatter = $this->getDataFormatter();
if($id) { if($id) {
$obj = DataObject::get_by_id($className, $id); $obj = DataObject::get_by_id($className, $id);
@ -208,6 +220,21 @@ class RestfulServer extends Controller {
return singleton($className)->buildDataObjectSet($query->execute()); return singleton($className)->buildDataObjectSet($query->execute());
} }
protected function getDataFormatter() {
$extension = $this->request->getExtension();
// Determine mime-type from extension
$contentType = isset(self::$mimetype_map[$extension]) ? self::$mimetype_map[$extension] : self::$default_mimetype;
if(!$extension) $extension = self::$default_extension;
$formatter = DataFormatter::for_extension($extension); //$this->dataFormatterFromMime($contentType);
if($customFields = $this->request->getVar('fields')) $formatter->setCustomFields(explode(',',$customFields));
$relationDepth = $this->request->getVar('relationdepth');
if(is_numeric($relationDepth)) $formatter->relationDepth = (int)$relationDepth;
return $formatter;
}
/** /**
* Handler for object delete * Handler for object delete
*/ */

View File

@ -30,10 +30,11 @@ class XMLDataFormatter extends DataFormatter {
$json = "<$className href=\"$objHref.xml\">\n"; $json = "<$className href=\"$objHref.xml\">\n";
foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) { foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
if(is_subclass_of($obj->$fieldName, 'Object') && $obj->hasMethod('toXML')) { $fieldValue = $obj->$fieldName;
$json .= $obj->$fieldName->toXML(); if(is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toXML')) {
$json .= $fieldValue->toXML();
} else { } else {
$json .= "<$fieldName>" . Convert::raw2xml($obj->$fieldName) . "</$fieldName>\n"; $json .= "<$fieldName>" . Convert::raw2xml($fieldValue) . "</$fieldName>\n";
} }
} }

View File

@ -1310,16 +1310,11 @@ class DataObject extends ViewableData implements DataObjectInterface {
public function scaffoldSearchFields() { public function scaffoldSearchFields() {
$fields = new FieldSet(); $fields = new FieldSet();
foreach($this->searchable_fields() as $fieldName => $fieldType) { foreach($this->searchable_fields() as $fieldName => $fieldType) {
if (is_int($fieldName)) $fieldName = $fieldType;
$field = $this->relObject($fieldName)->scaffoldSearchField(); $field = $this->relObject($fieldName)->scaffoldSearchField();
if (strstr($fieldName, '.')) { if (strstr($fieldName, '.')) {
$field->setName(str_replace('.', '__', $fieldName)); $field->setName(str_replace('.', '__', $fieldName));
$parts = explode('.', $fieldName);
//$label = $parts[count($parts)-2] . $parts[count($parts)-1];
$field->setTitle($this->toLabel($parts[count($parts)-2]));
} else {
$field->setTitle($this->toLabel($fieldName));
} }
$field->setTitle($this->searchable_fields_labels($fieldName));
$fields->push($field); $fields->push($field);
} }
return $fields; return $fields;
@ -1646,7 +1641,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return boolean * @return boolean
*/ */
public function canView($member = null) { public function canView($member = null) {
return true; return Permission::check('ADMIN');
} }
/** /**
@ -1654,7 +1649,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return boolean * @return boolean
*/ */
public function canEdit($member = null) { public function canEdit($member = null) {
return true; return Permission::check('ADMIN');
} }
/** /**
@ -1662,7 +1657,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return boolean * @return boolean
*/ */
public function canDelete($member = null) { public function canDelete($member = null) {
return true; return Permission::check('ADMIN');
} }
/** /**
@ -1672,7 +1667,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return boolean * @return boolean
*/ */
public function canCreate($member = null) { public function canCreate($member = null) {
return true; return Permission::check('ADMIN');
} }
/** /**
@ -2290,10 +2285,65 @@ class DataObject extends ViewableData implements DataObjectInterface {
$fields = $this->stat('searchable_fields'); $fields = $this->stat('searchable_fields');
if (!$fields) { if (!$fields) {
$fields = array_fill_keys(array_keys($this->summaryFields()), 'TextField'); $fields = array_fill_keys(array_keys($this->summaryFields()), 'TextField');
} else {
// rewrite array, if it is using shorthand syntax
$rewrite = array();
foreach($fields as $name => $type) {
if (is_int($name)) $rewrite[$type] = 'TextField';
else $rewrite[$name] = $type;
}
$fields = $rewrite;
} }
return $fields; return $fields;
} }
/**
* Get any user defined searchable fields labels that
* exist. Allows overriding of default field names in the form
* interface actually presented to the user.
*
* The reason for keeping this separate from searchable_fields,
* which would be a logical place for this functionality, is to
* avoid bloating and complicating the configuration array. Currently
* much of this system is based on sensible defaults, and this property
* would generally only be set in the case of more complex relationships
* between data object being required in the search interface.
*
* Generates labels based on name of the field itself, if no static property exists.
*
* @todo fix bad code
*
* @param $fieldName name of the field to retrieve
* @return array of all element labels if no argument given
* @return string of label if field
*/
public function searchable_fields_labels($fieldName=false) {
$labels = $this->stat('searchable_fields_labels');
if (is_array($labels)) {
if ($fieldName) {
if (isset($labels[$fieldName])) {
return $labels[$fieldName];
}
} else {
return $labels;
}
} else {
$fields = array_keys($this->searchable_fields());
$labels = array_combine($fields, $fields);
if ($fieldName) {
if (strstr($fieldName, '.')) {
$parts = explode('.', $fieldName);
$label = $parts[count($parts)-2] . ' ' . $parts[count($parts)-1];
return $this->toLabel($label);
} else {
return $this->toLabel($fieldName);
}
} else {
return $labels;
}
}
}
/** /**
* Get the default summary fields for this object. * Get the default summary fields for this object.
* *
@ -2353,7 +2403,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
if (is_subclass_of($type, 'SearchFilter')) { if (is_subclass_of($type, 'SearchFilter')) {
$filters[$name] = new $type($name); $filters[$name] = new $type($name);
} else { } else {
$filters[$name] = $this->relObject($name)->defaultSearchFilter(); $filters[$name] = $this->relObject($name)->defaultSearchFilter($name);
} }
} }
} }
@ -2540,6 +2590,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/ */
public static $searchable_fields = null; public static $searchable_fields = null;
/**
* User defined labels for searchable_fields, used to override
* default display in the search form.
*/
public static $searchable_fields_labels = null;
/** /**
* Provides a default list of fields to be used by a 'summary' * Provides a default list of fields to be used by a 'summary'
* view of this object. * view of this object.

View File

@ -142,7 +142,16 @@ class SQLQuery extends Object {
*/ */
public function leftJoin($table, $onPredicate) { public function leftJoin($table, $onPredicate) {
$this->from[] = "LEFT JOIN $table ON $onPredicate"; $this->from[] = "LEFT JOIN $table ON $onPredicate";
return $this;
}
/**
* Add an INNER JOIN criteria to the FROM clause.
*
* @return SQLQuery This instance
*/
public function innerJoin($table, $onPredicate) {
$this->from[] = "INNER JOIN $table ON $onPredicate";
return $this; return $this;
} }

View File

@ -217,12 +217,13 @@ abstract class DBField extends ViewableData {
* @todo documentation * @todo documentation
* *
* @todo figure out how we pass configuration parameters to * @todo figure out how we pass configuration parameters to
* search filters * search filters (note: parameter hack now in place to pass in the required full path - using $this->name won't work)
* *
* @return SearchFilter * @return SearchFilter
*/ */
public function defaultSearchFilter() { public function defaultSearchFilter($name = false) {
return new PartialMatchFilter($this->name); $name = ($name) ? $name : $this->name;
return new PartialMatchFilter($name);
} }
/** /**

View File

@ -110,12 +110,13 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
$parsedItems[] = $this->parseFixtureVal($item); $parsedItems[] = $this->parseFixtureVal($item);
} }
$obj->write(); $obj->write();
if($obj->many_many($fieldName)) { if($obj->has_many($fieldName)) {
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
} else {
$obj->getComponents($fieldName)->setByIDList($parsedItems); $obj->getComponents($fieldName)->setByIDList($parsedItems);
} elseif($obj->many_many($fieldName)) {
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
} }
} elseif($obj->has_one($fieldName)) {
$obj->{$fieldName . 'ID'} = $this->parseFixtureVal($fieldVal);
} else { } else {
$obj->$fieldName = $this->parseFixtureVal($fieldVal); $obj->$fieldName = $this->parseFixtureVal($fieldVal);
} }

View File

@ -1,4 +1,11 @@
<?php <?php
/**
* @package sapphire
* @subpackage misc
*/
/** /**
* Routines for DNS to country resolution * Routines for DNS to country resolution
* *
@ -278,7 +285,7 @@ class Geoip extends Object {
static function ip2country($address, $codeOnly = false) { static function ip2country($address, $codeOnly = false) {
// Detect internal networks - this is us, so we're NZ // Detect internal networks - this is us, so we're NZ
if(substr($address,0,7)=="192.168" || substr($address,0,4)=="127." || $address == "::1") { if(substr($address,0,7)=="192.168" || substr($address,0,4)=="127.") {
$code = "NZ"; $code = "NZ";
} else { } else {
$cmd = "geoiplookup ".escapeshellarg($address); $cmd = "geoiplookup ".escapeshellarg($address);

View File

@ -136,7 +136,6 @@ class SearchContext extends Object {
$SQL_sort = (!empty($sort)) ? Convert::raw2sql($sort) : singleton($this->modelClass)->stat('default_sort'); $SQL_sort = (!empty($sort)) ? Convert::raw2sql($sort) : singleton($this->modelClass)->stat('default_sort');
$query->orderby($SQL_sort); $query->orderby($SQL_sort);
foreach($searchParams as $key => $value) { foreach($searchParams as $key => $value) {
if ($value != '0') { if ($value != '0') {
$key = str_replace('__', '.', $key); $key = str_replace('__', '.', $key);
@ -148,7 +147,6 @@ class SearchContext extends Object {
} }
} }
} }
return $query; return $query;
} }
@ -164,6 +162,7 @@ class SearchContext extends Object {
*/ */
public function getResults($searchParams, $sort = false, $limit = false) { public function getResults($searchParams, $sort = false, $limit = false) {
$searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields')); $searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields'));
$query = $this->getQuery($searchParams, $sort, $limit); $query = $this->getQuery($searchParams, $sort, $limit);
// use if a raw SQL query is needed // use if a raw SQL query is needed
@ -187,27 +186,7 @@ class SearchContext extends Object {
function clearEmptySearchFields($value) { function clearEmptySearchFields($value) {
return ($value != ''); return ($value != '');
} }
/**
* @todo documentation
* @todo implementation
*
* @param array $searchFilters
* @param SQLQuery $query
*/
protected function processFilters(SQLQuery $query, $searchParams) {
/*$conditions = array();
foreach($this->filters as $field => $filter) {
if (strstr($field, '.')) {
$path = explode('.', $field);
} else {
$conditions[] = $filter->apply($searchParams[$field]);
}
}
$query->where = $conditions;
return $query;*/
}
/** /**
* Accessor for the filter attached to a named field. * Accessor for the filter attached to a named field.
* *
@ -231,6 +210,11 @@ class SearchContext extends Object {
return $this->filters; return $this->filters;
} }
/**
* Overwrite the current search context filter map.
*
* @param array $filters
*/
public function setFilters($filters) { public function setFilters($filters) {
$this->filters = $filters; $this->filters = $filters;
} }

View File

@ -21,6 +21,9 @@ class Group extends DataObject {
static $has_one = array( static $has_one = array(
"Parent" => "SiteTree", "Parent" => "SiteTree",
); );
static $has_many = array(
"Permissions" => "Permission",
);
static $many_many = array( static $many_many = array(
"Members" => "Member", "Members" => "Member",
); );

View File

@ -108,8 +108,8 @@ class Permission extends DataObject {
/** /**
* Check that the given member has the given permission * Check that the given member has the given permission
* @param int memberID The ID of the member to check. Leave blank for the * @param int|Member memberID The ID of the member to check. Leave blank for the current member.
* current member * Alternatively you can use a member object.
* @param string|array $code Code of the permission to check * @param string|array $code Code of the permission to check
* @param string $arg Optional argument (e.g. a permissions for a specific * @param string $arg Optional argument (e.g. a permissions for a specific
* page) * page)
@ -120,8 +120,9 @@ class Permission extends DataObject {
* disabled, TRUE will be returned if the permission does * disabled, TRUE will be returned if the permission does
* not exist at all. * not exist at all.
*/ */
public static function checkMember($memberID, $code, $arg = "any", $strict = true) { public static function checkMember($member, $code, $arg = "any", $strict = true) {
$perms_list = self::get_declared_permissions_list(); $perms_list = self::get_declared_permissions_list();
$memberID = (is_object($member)) ? $member->ID : $member;
if(self::$declared_permissions && is_array($perms_list) && if(self::$declared_permissions && is_array($perms_list) &&
!in_array($code, $perms_list)) { !in_array($code, $perms_list)) {
@ -146,7 +147,7 @@ class Permission extends DataObject {
if(is_numeric($arg)) { if(is_numeric($arg)) {
$argClause = "AND Arg IN (-1, $arg) "; $argClause = "AND Arg IN (-1, $arg) ";
} else { } else {
use_error("Permission::checkMember: bad arg '$arg'", user_error("Permission::checkMember: bad arg '$arg'",
E_USER_ERROR); E_USER_ERROR);
} }
} }