From c1440e0b02fc939d6b87a58b5b7715f919f3b891 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sat, 9 Aug 2008 06:53:26 +0000 Subject: [PATCH] (merged from branches/roa. use "svn log -c -g " for detailed commit message) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60234 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- api/JSONDataFormatter.php | 7 +-- api/RestfulServer.php | 73 +++++++++++++++++++--------- api/XMLDataFormatter.php | 7 +-- core/model/DataObject.php | 80 ++++++++++++++++++++++++++----- core/model/SQLQuery.php | 11 ++++- core/model/fieldtypes/DBField.php | 7 +-- dev/SapphireTest.php | 9 ++-- {security => misc}/Geoip.php | 9 +++- search/SearchContext.php | 30 +++--------- security/Group.php | 3 ++ security/Permission.php | 9 ++-- 11 files changed, 168 insertions(+), 77 deletions(-) rename {security => misc}/Geoip.php (99%) diff --git a/api/JSONDataFormatter.php b/api/JSONDataFormatter.php index 6ef355eb2..3e9771aff 100644 --- a/api/JSONDataFormatter.php +++ b/api/JSONDataFormatter.php @@ -23,10 +23,11 @@ class JSONDataFormatter extends DataFormatter { $json = "{\n className : \"$className\",\n"; foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) { - if(is_subclass_of($obj->$fieldName, 'Object') && $obj->hasMethod('toJSON')) { - $jsonParts[] = "$fieldName : " . $obj->$fieldName->toJSON(); + $fieldValue = $obj->$fieldName; + if(is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toJSON')) { + $jsonParts[] = "$fieldName : " . $fieldValue->toJSON(); } else { - $jsonParts[] = "$fieldName : \"" . Convert::raw2json($obj->$fieldName) . "\""; + $jsonParts[] = "$fieldName : " . Convert::raw2json($fieldValue); } } diff --git a/api/RestfulServer.php b/api/RestfulServer.php index 45c9b7771..ef45a0b8a 100644 --- a/api/RestfulServer.php +++ b/api/RestfulServer.php @@ -46,6 +46,34 @@ class RestfulServer extends Controller { 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) { return new RestfulServer_Item(DataObject::get_by_id($request->param("ClassName"), $request->param("ID"))); @@ -62,39 +90,22 @@ class RestfulServer extends Controller { */ function index() { ContentNegotiator::disable(); + $requestMethod = $_SERVER['REQUEST_METHOD']; if(!isset($this->urlParams['ClassName'])) return $this->notFound(); $className = $this->urlParams['ClassName']; $id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : 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) { case 'GET': - return $this->getHandler($className, $id, $relation, $formatter); + return $this->getHandler($className, $id, $relation); case 'PUT': - return $this->putHandler($className, $id, $relation, $formatter); + return $this->putHandler($className, $id, $relation); case 'DELETE': - return $this->deleteHandler($className, $id, $relation, $formatter); + return $this->deleteHandler($className, $id, $relation); case 'POST': } @@ -129,10 +140,9 @@ class RestfulServer extends Controller { * @param String $className * @param Int $id * @param String $relation - * @param String $contentType * @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' => $this->request->getVar('sort'), 'dir' => $this->request->getVar('dir') @@ -141,6 +151,8 @@ class RestfulServer extends Controller { 'start' => $this->request->getVar('start'), 'limit' => $this->request->getVar('limit') ); + + $formatter = $this->getDataFormatter(); if($id) { $obj = DataObject::get_by_id($className, $id); @@ -208,6 +220,21 @@ class RestfulServer extends Controller { 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 */ diff --git a/api/XMLDataFormatter.php b/api/XMLDataFormatter.php index 0a0b3c3c3..77626c150 100644 --- a/api/XMLDataFormatter.php +++ b/api/XMLDataFormatter.php @@ -30,10 +30,11 @@ class XMLDataFormatter extends DataFormatter { $json = "<$className href=\"$objHref.xml\">\n"; foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) { - if(is_subclass_of($obj->$fieldName, 'Object') && $obj->hasMethod('toXML')) { - $json .= $obj->$fieldName->toXML(); + $fieldValue = $obj->$fieldName; + if(is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toXML')) { + $json .= $fieldValue->toXML(); } else { - $json .= "<$fieldName>" . Convert::raw2xml($obj->$fieldName) . "\n"; + $json .= "<$fieldName>" . Convert::raw2xml($fieldValue) . "\n"; } } diff --git a/core/model/DataObject.php b/core/model/DataObject.php index a28aa46e9..aac63c36a 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -1310,16 +1310,11 @@ class DataObject extends ViewableData implements DataObjectInterface { public function scaffoldSearchFields() { $fields = new FieldSet(); foreach($this->searchable_fields() as $fieldName => $fieldType) { - if (is_int($fieldName)) $fieldName = $fieldType; $field = $this->relObject($fieldName)->scaffoldSearchField(); if (strstr($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); } return $fields; @@ -1646,7 +1641,7 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return boolean */ public function canView($member = null) { - return true; + return Permission::check('ADMIN'); } /** @@ -1654,7 +1649,7 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return boolean */ public function canEdit($member = null) { - return true; + return Permission::check('ADMIN'); } /** @@ -1662,7 +1657,7 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return boolean */ public function canDelete($member = null) { - return true; + return Permission::check('ADMIN'); } /** @@ -1672,7 +1667,7 @@ class DataObject extends ViewableData implements DataObjectInterface { * @return boolean */ 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'); if (!$fields) { $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; } - + + /** + * 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. * @@ -2353,7 +2403,7 @@ class DataObject extends ViewableData implements DataObjectInterface { if (is_subclass_of($type, 'SearchFilter')) { $filters[$name] = new $type($name); } 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; + /** + * 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' * view of this object. diff --git a/core/model/SQLQuery.php b/core/model/SQLQuery.php index d611894b1..8f0e8b1a8 100755 --- a/core/model/SQLQuery.php +++ b/core/model/SQLQuery.php @@ -142,7 +142,16 @@ class SQLQuery extends Object { */ public function leftJoin($table, $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; } diff --git a/core/model/fieldtypes/DBField.php b/core/model/fieldtypes/DBField.php index f5be348b0..8d7860e45 100644 --- a/core/model/fieldtypes/DBField.php +++ b/core/model/fieldtypes/DBField.php @@ -217,12 +217,13 @@ abstract class DBField extends ViewableData { * @todo documentation * * @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 */ - public function defaultSearchFilter() { - return new PartialMatchFilter($this->name); + public function defaultSearchFilter($name = false) { + $name = ($name) ? $name : $this->name; + return new PartialMatchFilter($name); } /** diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index 4c976ffaa..f8fc567df 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -110,12 +110,13 @@ class SapphireTest extends PHPUnit_Framework_TestCase { $parsedItems[] = $this->parseFixtureVal($item); } $obj->write(); - if($obj->many_many($fieldName)) { - $obj->getManyManyComponents($fieldName)->setByIDList($parsedItems); - } else { + if($obj->has_many($fieldName)) { $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 { $obj->$fieldName = $this->parseFixtureVal($fieldVal); } diff --git a/security/Geoip.php b/misc/Geoip.php similarity index 99% rename from security/Geoip.php rename to misc/Geoip.php index c31ef2b71..67552c745 100755 --- a/security/Geoip.php +++ b/misc/Geoip.php @@ -1,4 +1,11 @@ modelClass)->stat('default_sort'); $query->orderby($SQL_sort); - foreach($searchParams as $key => $value) { if ($value != '0') { $key = str_replace('__', '.', $key); @@ -148,7 +147,6 @@ class SearchContext extends Object { } } } - return $query; } @@ -164,6 +162,7 @@ class SearchContext extends Object { */ public function getResults($searchParams, $sort = false, $limit = false) { $searchParams = array_filter($searchParams, array($this,'clearEmptySearchFields')); + $query = $this->getQuery($searchParams, $sort, $limit); // use if a raw SQL query is needed @@ -187,27 +186,7 @@ class SearchContext extends Object { function clearEmptySearchFields($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. * @@ -231,6 +210,11 @@ class SearchContext extends Object { return $this->filters; } + /** + * Overwrite the current search context filter map. + * + * @param array $filters + */ public function setFilters($filters) { $this->filters = $filters; } diff --git a/security/Group.php b/security/Group.php index d5c0bc172..284702d4e 100644 --- a/security/Group.php +++ b/security/Group.php @@ -21,6 +21,9 @@ class Group extends DataObject { static $has_one = array( "Parent" => "SiteTree", ); + static $has_many = array( + "Permissions" => "Permission", + ); static $many_many = array( "Members" => "Member", ); diff --git a/security/Permission.php b/security/Permission.php index 9bb0d6b90..2c16ae54f 100755 --- a/security/Permission.php +++ b/security/Permission.php @@ -108,8 +108,8 @@ class Permission extends DataObject { /** * Check that the given member has the given permission - * @param int memberID The ID of the member to check. Leave blank for the - * current member + * @param int|Member memberID The ID of the member to check. Leave blank for the current member. + * Alternatively you can use a member object. * @param string|array $code Code of the permission to check * @param string $arg Optional argument (e.g. a permissions for a specific * page) @@ -120,8 +120,9 @@ class Permission extends DataObject { * disabled, TRUE will be returned if the permission does * 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(); + $memberID = (is_object($member)) ? $member->ID : $member; if(self::$declared_permissions && is_array($perms_list) && !in_array($code, $perms_list)) { @@ -146,7 +147,7 @@ class Permission extends DataObject { if(is_numeric($arg)) { $argClause = "AND Arg IN (-1, $arg) "; } else { - use_error("Permission::checkMember: bad arg '$arg'", + user_error("Permission::checkMember: bad arg '$arg'", E_USER_ERROR); } }