Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

API CHANGE Moved RestfulServer into its own module at https://github.…

  • Loading branch information...
commit cb8b11812ce8eed409d0ef6abf6d6a7643c76a5a 1 parent a757c16
Ingo Schommer chillu authored
2  api/FormEncodedDataFormatter.php
View
@@ -2,8 +2,6 @@
/**
* Accepts form encoded strings and converts them
* to a valid PHP array via {@link parse_str()}.
- * Use together with {@link RESTfulServer} to submit
- * data via POST or PUT.
*
* Example when using cURL on commandline:
* <code>
660 api/RestfulServer.php
View
@@ -1,660 +0,0 @@
-<?php
-/**
- * SilverStripe's generic RESTful server.
- *
- * This class gives your application a RESTful API for free. All you have to do is define static $api_access = true on
- * the appropriate DataObjects. You will need to ensure that all of your data manipulation and security is defined in
- * your model layer (ie, the DataObject classes) and not in your Controllers. This is the recommended design for SilverStripe
- * applications.
- *
- * Enabling restful access on a model will also enable a SOAP API, see {@link SOAPModelAccess}.
- *
- * Example DataObject with simple api access, giving full access to all object properties and relations,
- * unless explicitly controlled through model permissions.
- * <code>
- * class Article extends DataObject {
- * static $db = array('Title'=>'Text','Published'=>'Boolean');
- * static $api_access = true;
- * }
- * </code>
- *
- * * Example DataObject with advanced api access, limiting viewing and editing to Title attribute only:
- * <code>
- * class Article extends DataObject {
- * static $db = array('Title'=>'Text','Published'=>'Boolean');
- * static $api_access = array(
- * 'view' => array('Title'),
- * 'edit' => array('Title'),
- * );
- * }
- * </code>
- *
- * <b>Supported operations</b>
- *
- * - GET /api/v1/(ClassName)/(ID) - gets a database record
- * - GET /api/v1/(ClassName)/(ID)/(Relation) - get all of the records linked to this database record by the given reatlion
- * - GET /api/v1/(ClassName)?(Field)=(Val)&(Field)=(Val) - searches for matching database records
- * - POST /api/v1/(ClassName) - create a new database record
- * - PUT /api/v1/(ClassName)/(ID) - updates a database record
- * - PUT /api/v1/(ClassName)/(ID)/(Relation) - updates a relation, replacing the existing record(s) (NOT IMPLEMENTED YET)
- * - POST /api/v1/(ClassName)/(ID)/(Relation) - updates a relation, appending to the existing record(s) (NOT IMPLEMENTED YET)
- *
- * - DELETE /api/v1/(ClassName)/(ID) - deletes a database record (NOT IMPLEMENTED YET)
- * - DELETE /api/v1/(ClassName)/(ID)/(Relation)/(ForeignID) - remove the relationship between two database records, but don't actually delete the foreign object (NOT IMPLEMENTED YET)
- *
- * - POST /api/v1/(ClassName)/(ID)/(MethodName) - executes a method on the given object (e.g, publish)
- *
- * <b>Search</b>
- *
- * You can trigger searches based on the fields specified on {@link DataObject::searchable_fields} and passed
- * through {@link DataObject::getDefaultSearchContext()}. Just add a key-value pair with the search-term
- * to the url, e.g. /api/v1/(ClassName)/?Title=mytitle.
- *
- * <b>Other url-modifiers</b>
- *
- * - &limit=<numeric>: Limit the result set
- * - &relationdepth=<numeric>: Displays links to existing has-one and has-many relationships to a certain depth (Default: 1)
- * - &fields=<string>: Comma-separated list of fields on the output object (defaults to all database-columns).
- * Handy to limit output for bandwidth and performance reasons.
- * - &sort=<myfield>&dir=<asc|desc>
- * - &add_fields=<string>: Comma-separated list of additional fields, for example dynamic getters.
- *
- * <b>Access control</b>
- *
- * Access control is implemented through the usual Member system with Basicauth authentication only.
- * By default, you have to bear the ADMIN permission to retrieve or send any data.
- *
- * You should override the following built-in methods to customize permission control on a
- * class- and object-level:
- * - {@link DataObject::canView()}
- * - {@link DataObject::canEdit()}
- * - {@link DataObject::canDelete()}
- * - {@link DataObject::canCreate()}
- * See {@link DataObject} documentation for further details.
- *
- * You can specify the character-encoding for any input on the HTTP Content-Type.
- * At the moment, only UTF-8 is supported. All output is made in UTF-8 regardless of Accept headers.
- *
- * @todo Finish RestfulServer_Item and RestfulServer_List implementation and re-enable $url_handlers
- * @todo Implement PUT/POST/DELETE for relations
- * @todo Access-Control for relations (you might be allowed to view Members and Groups, but not their relation with each other)
- * @todo Make SearchContext specification customizeable for each class
- * @todo Allow for range-searches (e.g. on Created column)
- * @todo Allow other authentication methods (currently only HTTP BasicAuth)
- * @todo Filter relation listings by $api_access and canView() permissions
- * @todo Exclude relations when "fields" are specified through URL (they should be explicitly requested in this case)
- * @todo Custom filters per DataObject subclass, e.g. to disallow showing unpublished pages in SiteTree/Versioned/Hierarchy
- * @todo URL parameter namespacing for search-fields, limit, fields, add_fields (might all be valid dataobject properties)
- * e.g. you wouldn't be able to search for a "limit" property on your subclass as its overlayed with the search logic
- * @todo i18n integration (e.g. Page/1.xml?lang=de_DE)
- * @todo Access to extendable methods/relations like SiteTree/1/Versions or SiteTree/1/Version/22
- * @todo Respect $api_access array notation in search contexts
- *
- * @package framework
- * @subpackage api
- */
-class RestfulServer extends Controller {
- static $url_handlers = array(
- '$ClassName/$ID/$Relation' => 'handleAction'
- #'$ClassName/#ID' => 'handleItem',
- #'$ClassName' => 'handleList',
- );
-
- 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
- */
- public static $default_extension = "xml";
-
- /**
- * If no extension is given, resolve the request to this mimetype.
- *
- * @var string
- */
- protected static $default_mimetype = "text/xml";
-
- /**
- * @uses authenticate()
- * @var Member
- */
- protected $member;
-
- static $allowed_actions = array(
- 'index'
- );
-
- /*
- function handleItem($request) {
- return new RestfulServer_Item(DataObject::get_by_id($request->param("ClassName"), $request->param("ID")));
- }
-
- function handleList($request) {
- return new RestfulServer_List(DataObject::get($request->param("ClassName"),""));
- }
- */
-
- /**
- * This handler acts as the switchboard for the controller.
- * Since no $Action url-param is set, all requests are sent here.
- */
- function index() {
- 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;
-
- // Check input formats
- if(!class_exists($className)) return $this->notFound();
- if($id && !is_numeric($id)) return $this->notFound();
- if($relation && !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $relation)) return $this->notFound();
-
- // if api access is disabled, don't proceed
- $apiAccess = singleton($className)->stat('api_access');
- if(!$apiAccess) return $this->permissionFailure();
-
- // authenticate through HTTP BasicAuth
- $this->member = $this->authenticate();
-
- // handle different HTTP verbs
- if($this->request->isGET() || $this->request->isHEAD()) return $this->getHandler($className, $id, $relation);
- if($this->request->isPOST()) return $this->postHandler($className, $id, $relation);
- if($this->request->isPUT()) return $this->putHandler($className, $id, $relation);
- if($this->request->isDELETE()) return $this->deleteHandler($className, $id, $relation);
-
- // if no HTTP verb matches, return error
- return $this->methodNotAllowed();
- }
-
- /**
- * Handler for object read.
- *
- * The data object will be returned in the following format:
- *
- * <ClassName>
- * <FieldName>Value</FieldName>
- * ...
- * <HasOneRelName id="ForeignID" href="LinkToForeignRecordInAPI" />
- * ...
- * <HasManyRelName>
- * <ForeignClass id="ForeignID" href="LinkToForeignRecordInAPI" />
- * <ForeignClass id="ForeignID" href="LinkToForeignRecordInAPI" />
- * </HasManyRelName>
- * ...
- * <ManyManyRelName>
- * <ForeignClass id="ForeignID" href="LinkToForeignRecordInAPI" />
- * <ForeignClass id="ForeignID" href="LinkToForeignRecordInAPI" />
- * </ManyManyRelName>
- * </ClassName>
- *
- * Access is controlled by two variables:
- *
- * - 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
- *
- * @todo Access checking
- *
- * @param String $className
- * @param Int $id
- * @param String $relation
- * @return String The serialized representation of the requested object(s) - usually XML or JSON.
- */
- protected function getHandler($className, $id, $relationName) {
- $sort = '';
-
- if($this->request->getVar('sort')) {
- $dir = $this->request->getVar('dir');
- $sort = array($this->request->getVar('sort') => ($dir ? $dir : 'ASC'));
- }
-
- $limit = array(
- 'start' => $this->request->getVar('start'),
- 'limit' => $this->request->getVar('limit')
- );
-
- $params = $this->request->getVars();
-
- $responseFormatter = $this->getResponseDataFormatter($className);
- if(!$responseFormatter) return $this->unsupportedMediaType();
-
- // $obj can be either a DataObject or a SS_List,
- // depending on the request
- if($id) {
- // Format: /api/v1/<MyClass>/<ID>
- $obj = $this->getObjectQuery($className, $id, $params)->First();
- if(!$obj) return $this->notFound();
- if(!$obj->canView()) return $this->permissionFailure();
-
- // Format: /api/v1/<MyClass>/<ID>/<Relation>
- if($relationName) {
- $obj = $this->getObjectRelationQuery($obj, $params, $sort, $limit, $relationName);
- if(!$obj) return $this->notFound();
-
- // TODO Avoid creating data formatter again for relation class (see above)
- $responseFormatter = $this->getResponseDataFormatter($obj->dataClass());
- }
-
- } else {
- // Format: /api/v1/<MyClass>
- $obj = $this->getObjectsQuery($className, $params, $sort, $limit);
- }
-
- $this->getResponse()->addHeader('Content-Type', $responseFormatter->getOutputContentType());
-
- $rawFields = $this->request->getVar('fields');
- $fields = $rawFields ? explode(',', $rawFields) : null;
-
- if($obj instanceof SS_List) {
- $responseFormatter->setTotalSize($obj->dataQuery()->query()->unlimitedRowCount());
- return $responseFormatter->convertDataObjectSet($obj, $fields);
- } else if(!$obj) {
- $responseFormatter->setTotalSize(0);
- return $responseFormatter->convertDataObjectSet(new ArrayList(), $fields);
- } else {
- return $responseFormatter->convertDataObject($obj, $fields);
- }
- }
-
- /**
- * Uses the default {@link SearchContext} specified through
- * {@link DataObject::getDefaultSearchContext()} to augument
- * an existing query object (mostly a component query from {@link DataObject})
- * with search clauses.
- *
- * @todo Allow specifying of different searchcontext getters on model-by-model basis
- *
- * @param string $className
- * @param array $params
- * @return SS_List
- */
- protected function getSearchQuery($className, $params = null, $sort = null, $limit = null, $existingQuery = null) {
- if(singleton($className)->hasMethod('getRestfulSearchContext')) {
- $searchContext = singleton($className)->{'getRestfulSearchContext'}();
- } else {
- $searchContext = singleton($className)->getDefaultSearchContext();
- }
- return $searchContext->getQuery($params, $sort, $limit, $existingQuery);
- }
-
- /**
- * Returns a dataformatter instance based on the request
- * extension or mimetype. Falls back to {@link self::$default_extension}.
- *
- * @param boolean $includeAcceptHeader Determines wether to inspect and prioritize any HTTP Accept headers
- * @param String Classname of a DataObject
- * @return DataFormatter
- */
- protected function getDataFormatter($includeAcceptHeader = false, $className = null) {
- $extension = $this->request->getExtension();
- $contentTypeWithEncoding = $this->request->getHeader('Content-Type');
- preg_match('/([^;]*)/',$contentTypeWithEncoding, $contentTypeMatches);
- $contentType = $contentTypeMatches[0];
- $accept = $this->request->getHeader('Accept');
- $mimetypes = $this->request->getAcceptMimetypes();
- if(!$className) $className = $this->urlParams['ClassName'];
-
- // get formatter
- if(!empty($extension)) {
- $formatter = DataFormatter::for_extension($extension);
- }elseif($includeAcceptHeader && !empty($accept) && $accept != '*/*') {
- $formatter = DataFormatter::for_mimetypes($mimetypes);
- if(!$formatter) $formatter = DataFormatter::for_extension(self::$default_extension);
- } elseif(!empty($contentType)) {
- $formatter = DataFormatter::for_mimetype($contentType);
- } else {
- $formatter = DataFormatter::for_extension(self::$default_extension);
- }
-
- if(!$formatter) return false;
-
- // set custom fields
- if($customAddFields = $this->request->getVar('add_fields')) $formatter->setCustomAddFields(explode(',',$customAddFields));
- if($customFields = $this->request->getVar('fields')) $formatter->setCustomFields(explode(',',$customFields));
- $formatter->setCustomRelations($this->getAllowedRelations($className));
-
- $apiAccess = singleton($className)->stat('api_access');
- if(is_array($apiAccess)) {
- $formatter->setCustomAddFields(array_intersect((array)$formatter->getCustomAddFields(), (array)$apiAccess['view']));
- if($formatter->getCustomFields()) {
- $formatter->setCustomFields(array_intersect((array)$formatter->getCustomFields(), (array)$apiAccess['view']));
- } else {
- $formatter->setCustomFields((array)$apiAccess['view']);
- }
- if($formatter->getCustomRelations()) {
- $formatter->setCustomRelations(array_intersect((array)$formatter->getCustomRelations(), (array)$apiAccess['view']));
- } else {
- $formatter->setCustomRelations((array)$apiAccess['view']);
- }
-
- }
-
- // set relation depth
- $relationDepth = $this->request->getVar('relationdepth');
- if(is_numeric($relationDepth)) $formatter->relationDepth = (int)$relationDepth;
-
- return $formatter;
- }
-
- /**
- * @param String Classname of a DataObject
- * @return DataFormatter
- */
- protected function getRequestDataFormatter($className = null) {
- return $this->getDataFormatter(false, $className);
- }
-
- /**
- * @param String Classname of a DataObject
- * @return DataFormatter
- */
- protected function getResponseDataFormatter($className = null) {
- return $this->getDataFormatter(true, $className);
- }
-
- /**
- * Handler for object delete
- */
- protected function deleteHandler($className, $id) {
- $obj = DataObject::get_by_id($className, $id);
- if(!$obj) return $this->notFound();
- if(!$obj->canDelete()) return $this->permissionFailure();
-
- $obj->delete();
-
- $this->getResponse()->setStatusCode(204); // No Content
- return true;
- }
-
- /**
- * Handler for object write
- */
- protected function putHandler($className, $id) {
- $obj = DataObject::get_by_id($className, $id);
- if(!$obj) return $this->notFound();
- if(!$obj->canEdit()) return $this->permissionFailure();
-
- $reqFormatter = $this->getRequestDataFormatter($className);
- if(!$reqFormatter) return $this->unsupportedMediaType();
-
- $responseFormatter = $this->getResponseDataFormatter($className);
- if(!$responseFormatter) return $this->unsupportedMediaType();
-
- $obj = $this->updateDataObject($obj, $reqFormatter);
-
- $this->getResponse()->setStatusCode(200); // Success
- $this->getResponse()->addHeader('Content-Type', $responseFormatter->getOutputContentType());
-
- // Append the default extension for the output format to the Location header
- // or else we'll use the default (XML)
- $types = $responseFormatter->supportedExtensions();
- $type = '';
- if (count($types)) {
- $type = ".{$types[0]}";
- }
-
- $objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID" . $type);
- $this->getResponse()->addHeader('Location', $objHref);
-
- return $responseFormatter->convertDataObject($obj);
- }
-
- /**
- * Handler for object append / method call.
- *
- * @todo Posting to an existing URL (without a relation)
- * current resolves in creatig a new element,
- * rather than a "Conflict" message.
- */
- protected function postHandler($className, $id, $relation) {
- if($id) {
- if(!$relation) {
- $this->response->setStatusCode(409);
- return 'Conflict';
- }
-
- $obj = DataObject::get_by_id($className, $id);
- if(!$obj) return $this->notFound();
-
- if(!$obj->hasMethod($relation)) {
- return $this->notFound();
- }
-
- if(!$obj->stat('allowed_actions') || !in_array($relation, $obj->stat('allowed_actions'))) {
- return $this->permissionFailure();
- }
-
- $obj->$relation();
-
- $this->getResponse()->setStatusCode(204); // No Content
- return true;
- } else {
- if(!singleton($className)->canCreate()) return $this->permissionFailure();
- $obj = new $className();
-
- $reqFormatter = $this->getRequestDataFormatter($className);
- if(!$reqFormatter) return $this->unsupportedMediaType();
-
- $responseFormatter = $this->getResponseDataFormatter($className);
-
- $obj = $this->updateDataObject($obj, $reqFormatter);
-
- $this->getResponse()->setStatusCode(201); // Created
- $this->getResponse()->addHeader('Content-Type', $responseFormatter->getOutputContentType());
-
- // Append the default extension for the output format to the Location header
- // or else we'll use the default (XML)
- $types = $responseFormatter->supportedExtensions();
- $type = '';
- if (count($types)) {
- $type = ".{$types[0]}";
- }
-
- $objHref = Director::absoluteURL(self::$api_base . "$obj->class/$obj->ID" . $type);
- $this->getResponse()->addHeader('Location', $objHref);
-
- return $responseFormatter->convertDataObject($obj);
- }
- }
-
- /**
- * Converts either the given HTTP Body into an array
- * (based on the DataFormatter instance), or returns
- * the POST variables.
- * Automatically filters out certain critical fields
- * that shouldn't be set by the client (e.g. ID).
- *
- * @param DataObject $obj
- * @param DataFormatter $formatter
- * @return DataObject The passed object
- */
- protected function updateDataObject($obj, $formatter) {
- // if neither an http body nor POST data is present, return error
- $body = $this->request->getBody();
- if(!$body && !$this->request->postVars()) {
- $this->getResponse()->setStatusCode(204); // No Content
- return 'No Content';
- }
-
- if(!empty($body)) {
- $data = $formatter->convertStringToArray($body);
- } else {
- // assume application/x-www-form-urlencoded which is automatically parsed by PHP
- $data = $this->request->postVars();
- }
-
- // @todo Disallow editing of certain keys in database
- $data = array_diff_key($data, array('ID','Created'));
-
- $apiAccess = singleton($this->urlParams['ClassName'])->stat('api_access');
- if(is_array($apiAccess) && isset($apiAccess['edit'])) {
- $data = array_intersect_key($data, array_combine($apiAccess['edit'],$apiAccess['edit']));
- }
-
- $obj->update($data);
- $obj->write();
-
- return $obj;
- }
-
- /**
- * Gets a single DataObject by ID,
- * through a request like /api/v1/<MyClass>/<MyID>
- *
- * @param string $className
- * @param int $id
- * @param array $params
- * @return DataList
- */
- protected function getObjectQuery($className, $id, $params) {
- return DataList::create($className)->byIDs(array($id));
- }
-
- /**
- * @param DataObject $obj
- * @param array $params
- * @param int|array $sort
- * @param int|array $limit
- * @return SQLQuery
- */
- protected function getObjectsQuery($className, $params, $sort, $limit) {
- return $this->getSearchQuery($className, $params, $sort, $limit);
- }
-
-
- /**
- * @param DataObject $obj
- * @param array $params
- * @param int|array $sort
- * @param int|array $limit
- * @param string $relationName
- * @return SQLQuery|boolean
- */
- protected function getObjectRelationQuery($obj, $params, $sort, $limit, $relationName) {
- // The relation method will return a DataList, that getSearchQuery subsequently manipulates
- if($obj->hasMethod($relationName)) {
- if($relationClass = $obj->has_one($relationName)) {
- $joinField = $relationName . 'ID';
- $list = DataList::create($relationClass)->byIDs(array($obj->$joinField));
- } else {
- $list = $obj->$relationName();
- }
-
- $apiAccess = singleton($list->dataClass())->stat('api_access');
- if(!$apiAccess) return false;
-
- return $this->getSearchQuery($list->dataClass(), $params, $sort, $limit, $list);
- }
- }
-
- protected function permissionFailure() {
- // return a 401
- $this->getResponse()->setStatusCode(401);
- $this->getResponse()->addHeader('WWW-Authenticate', 'Basic realm="API Access"');
- $this->getResponse()->addHeader('Content-Type', 'text/plain');
- return "You don't have access to this item through the API.";
- }
-
- protected function notFound() {
- // return a 404
- $this->getResponse()->setStatusCode(404);
- $this->getResponse()->addHeader('Content-Type', 'text/plain');
- return "That object wasn't found";
- }
-
- protected function methodNotAllowed() {
- $this->getResponse()->setStatusCode(405);
- $this->getResponse()->addHeader('Content-Type', 'text/plain');
- return "Method Not Allowed";
- }
-
- protected function unsupportedMediaType() {
- $this->response->setStatusCode(415); // Unsupported Media Type
- $this->getResponse()->addHeader('Content-Type', 'text/plain');
- return "Unsupported Media Type";
- }
-
- protected function authenticate() {
- if(!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) return false;
-
- if($member = Member::currentUser()) return $member;
- $member = MemberAuthenticator::authenticate(array(
- 'Email' => $_SERVER['PHP_AUTH_USER'],
- 'Password' => $_SERVER['PHP_AUTH_PW'],
- ), null);
-
- if($member) {
- $member->LogIn(false);
- return $member;
- } else {
- return false;
- }
- }
-
- /**
- * Return only relations which have $api_access enabled.
- * @todo Respect field level permissions once they are available in core
- *
- * @param string $class
- * @param Member $member
- * @return array
- */
- protected function getAllowedRelations($class, $member = null) {
- $allowedRelations = array();
- $obj = singleton($class);
- $relations = (array)$obj->has_one() + (array)$obj->has_many() + (array)$obj->many_many();
- if($relations) foreach($relations as $relName => $relClass) {
- if(singleton($relClass)->stat('api_access')) {
- $allowedRelations[] = $relName;
- }
- }
- return $allowedRelations;
- }
-
-}
-
-/**
- * Restful server handler for a SS_List
- *
- * @package framework
- * @subpackage api
- */
-class RestfulServer_List {
- static $url_handlers = array(
- '#ID' => 'handleItem',
- );
-
- function __construct($list) {
- $this->list = $list;
- }
-
- function handleItem($request) {
- return new RestulServer_Item($this->list->getById($request->param('ID')));
- }
-}
-
-/**
- * Restful server handler for a single DataObject
- *
- * @package framework
- * @subpackage api
- */
-class RestfulServer_Item {
- static $url_handlers = array(
- '$Relation' => 'handleRelation',
- );
-
- function __construct($item) {
- $this->item = $item;
- }
-
- function handleRelation($request) {
- $funcName = $request('Relation');
- $relation = $this->item->$funcName();
-
- if($relation instanceof SS_List) return new RestfulServer_List($relation);
- else return new RestfulServer_Item($relation);
- }
-}
23 api/VersionedRestfulServer.php
View
@@ -1,23 +0,0 @@
-<?php
-/**
- * Simple wrapper to allow access to the live site via REST
- *
- * @package framework
- * @subpackage integration
- */
-class VersionedRestfulServer extends Controller {
-
- static $allowed_actions = array(
- 'index'
- );
-
- function handleRequest(SS_HTTPRequest $request, DataModel $model) {
- $this->setDataModel($model);
- Versioned::reading_stage('Live');
- $restfulserver = new RestfulServer();
- $response = $restfulserver->handleRequest($request, $model);
- return $response;
- }
-}
-
-
2  control/HTTPRequest.php
View
@@ -170,7 +170,7 @@ function requestVar($name) {
* Returns a possible file extension found in parsing the URL
* as denoted by a "."-character near the end of the URL.
* Doesn't necessarily have to belong to an existing file,
- * for example used for {@link RestfulServer} content-type-switching.
+ * as extensions can be also used for content-type-switching.
*
* @return string
*/
4 docs/en/changelogs/3.0.0.md
View
@@ -518,6 +518,10 @@ Use a [custom alias via function scope](http://api.jquery.com/jQuery.noConflict/
See [module on github](https://github.com/silverstripe/silverstripe-widgets).
+### Moved `RestfulServer` and `SapphireSoapServer` API into new modules###
+
+See ["restfulserver"] and ["soapserver"] modules on github.
+
### Moved `Translatable` extension into new 'translatable' module ###
If you are translating your `SiteTree` or `DataObject` classes with the `Translatable`
2  docs/en/reference/searchcontext.md
View
@@ -188,5 +188,5 @@ See `[api:SearchFilter]` API Documentation
## Related
* `[api:ModelAdmin]`
-* `[api:RestfulServer]`
+* [RestfulServer module](https://github.com/silverstripe/silverstripe-restfulserver)
* [Tutorial: Site Search](/tutorials/4-site-search)
2  docs/en/topics/search.md
View
@@ -34,7 +34,7 @@ dedicated search service like the [sphinx module](http://silverstripe.org/sphinx
## Related
* `[api:ModelAdmin]`
-* `[api:RestfulServer]`
+* [RestfulServer module](https://github.com/silverstripe/silverstripe-restfulserver)
* [Tutorial: Site Search](/tutorials/4-site-search)
* [SearchContext](/reference/searchcontext)
* [genericviews module](http://silverstripe.org/generic-views-module)
567 tests/api/RestfulServerTest.php
View
@@ -1,567 +0,0 @@
-<?php
-/**
- *
- * @todo Test Relation getters
- * @todo Test filter and limit through GET params
- * @todo Test DELETE verb
- *
- */
-class RestfulServerTest extends SapphireTest {
-
- static $fixture_file = 'RestfulServerTest.yml';
-
- protected $extraDataObjects = array(
- 'RestfulServerTest_Comment',
- 'RestfulServerTest_SecretThing',
- 'RestfulServerTest_Page',
- 'RestfulServerTest_Author',
- 'RestfulServerTest_AuthorRating',
- );
-
- public function testApiAccess() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
- $page1 = $this->objFromFixture('RestfulServerTest_Page', 'page1');
-
- // normal GET should succeed with $api_access enabled
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 200);
-
- $_SERVER['PHP_AUTH_USER'] = 'user@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'user';
-
- // even with logged in user a GET with $api_access disabled should fail
- $url = "/api/v1/RestfulServerTest_Page/" . $page1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 401);
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testApiAccessBoolean() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertContains('<ID>', $response->getBody());
- $this->assertContains('<Name>', $response->getBody());
- $this->assertContains('<Comment>', $response->getBody());
- $this->assertContains('<Page', $response->getBody());
- $this->assertContains('<Author', $response->getBody());
- }
-
- public function testAuthenticatedGET() {
- $thing1 = $this->objFromFixture('RestfulServerTest_SecretThing', 'thing1');
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- // @todo create additional mock object with authenticated VIEW permissions
- $url = "/api/v1/RestfulServerTest_SecretThing/" . $thing1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 401);
-
- $_SERVER['PHP_AUTH_USER'] = 'user@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'user';
-
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 200);
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testAuthenticatedPUT() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $data = array('Comment' => 'created');
-
- $response = Director::test($url, $data, null, 'PUT');
- $this->assertEquals($response->getStatusCode(), 401); // Permission failure
-
- $_SERVER['PHP_AUTH_USER'] = 'editor@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'editor';
- $response = Director::test($url, $data, null, 'PUT');
- $this->assertEquals($response->getStatusCode(), 200); // Success
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testGETRelationshipsXML() {
- $author1 = $this->objFromFixture('RestfulServerTest_Author', 'author1');
- $rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating', 'rating1');
- $rating2 = $this->objFromFixture('RestfulServerTest_AuthorRating', 'rating2');
-
- // @todo should be set up by fixtures, doesn't work for some reason...
- $author1->Ratings()->add($rating1);
- $author1->Ratings()->add($rating2);
-
- $url = "/api/v1/RestfulServerTest_Author/" . $author1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 200);
-
- $responseArr = Convert::xml2array($response->getBody());
- $ratingsArr = $responseArr['Ratings']['RestfulServerTest_AuthorRating'];
- $this->assertEquals(count($ratingsArr), 2);
- $ratingIDs = array(
- (int)$ratingsArr[0]['@attributes']['id'],
- (int)$ratingsArr[1]['@attributes']['id']
- );
- $this->assertContains($rating1->ID, $ratingIDs);
- $this->assertContains($rating2->ID, $ratingIDs);
- }
-
- public function testGETManyManyRelationshipsXML() {
- // author4 has related authors author2 and author3
- $author2 = $this->objFromFixture('RestfulServerTest_Author', 'author2');
- $author3 = $this->objFromFixture('RestfulServerTest_Author', 'author3');
- $author4 = $this->objFromFixture('RestfulServerTest_Author', 'author4');
-
- $url = "/api/v1/RestfulServerTest_Author/" . $author4->ID . '/RelatedAuthors';
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals(200, $response->getStatusCode());
- $arr = Convert::xml2array($response->getBody());
- $authorsArr = $arr['RestfulServerTest_Author'];
-
- $this->assertEquals(count($authorsArr), 2);
- $ratingIDs = array(
- (int)$authorsArr[0]['ID'],
- (int)$authorsArr[1]['ID']
- );
- $this->assertContains($author2->ID, $ratingIDs);
- $this->assertContains($author3->ID, $ratingIDs);
- }
-
- public function testPUTWithFormEncoded() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $_SERVER['PHP_AUTH_USER'] = 'editor@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'editor';
-
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $body = 'Name=Updated Comment&Comment=updated';
- $headers = array(
- 'Content-Type' => 'application/x-www-form-urlencoded'
- );
- $response = Director::test($url, null, null, 'PUT', $body, $headers);
- $this->assertEquals($response->getStatusCode(), 200); // Success
- // Assumption: XML is default output
- $responseArr = Convert::xml2array($response->getBody());
- $this->assertEquals($responseArr['ID'], $comment1->ID);
- $this->assertEquals($responseArr['Comment'], 'updated');
- $this->assertEquals($responseArr['Name'], 'Updated Comment');
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testPOSTWithFormEncoded() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $_SERVER['PHP_AUTH_USER'] = 'editor@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'editor';
-
- $url = "/api/v1/RestfulServerTest_Comment";
- $body = 'Name=New Comment&Comment=created';
- $headers = array(
- 'Content-Type' => 'application/x-www-form-urlencoded'
- );
- $response = Director::test($url, null, null, 'POST', $body, $headers);
- $this->assertEquals($response->getStatusCode(), 201); // Created
- // Assumption: XML is default output
- $responseArr = Convert::xml2array($response->getBody());
- $this->assertTrue($responseArr['ID'] > 0);
- $this->assertNotEquals($responseArr['ID'], $comment1->ID);
- $this->assertEquals($responseArr['Comment'], 'created');
- $this->assertEquals($responseArr['Name'], 'New Comment');
- $this->assertEquals($response->getHeader('Location'), Controller::join_links(Director::absoluteBaseURL(), $url, $responseArr['ID']));
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testPUTwithJSON() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $_SERVER['PHP_AUTH_USER'] = 'editor@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'editor';
-
- // by mimetype
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $body = '{"Comment":"updated"}';
- $response = Director::test($url, null, null, 'PUT', $body, array('Content-Type'=>'application/json'));
- $this->assertEquals($response->getStatusCode(), 200); // Updated
- $obj = Convert::json2obj($response->getBody());
- $this->assertEquals($obj->ID, $comment1->ID);
- $this->assertEquals($obj->Comment, 'updated');
-
- // by extension
- $url = sprintf("/api/v1/RestfulServerTest_Comment/%d.json", $comment1->ID);
- $body = '{"Comment":"updated"}';
- $response = Director::test($url, null, null, 'PUT', $body);
- $this->assertEquals($response->getStatusCode(), 200); // Updated
- $this->assertEquals($response->getHeader('Location'), Controller::join_links(Director::absoluteBaseURL(), $url));
- $obj = Convert::json2obj($response->getBody());
- $this->assertEquals($obj->ID, $comment1->ID);
- $this->assertEquals($obj->Comment, 'updated');
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testPUTwithXML() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $_SERVER['PHP_AUTH_USER'] = 'editor@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'editor';
-
- // by mimetype
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $body = '<RestfulServerTest_Comment><Comment>updated</Comment></RestfulServerTest_Comment>';
- $response = Director::test($url, null, null, 'PUT', $body, array('Content-Type'=>'text/xml'));
- $this->assertEquals($response->getStatusCode(), 200); // Updated
- $obj = Convert::xml2array($response->getBody());
- $this->assertEquals($obj['ID'], $comment1->ID);
- $this->assertEquals($obj['Comment'], 'updated');
-
- // by extension
- $url = sprintf("/api/v1/RestfulServerTest_Comment/%d.xml", $comment1->ID);
- $body = '<RestfulServerTest_Comment><Comment>updated</Comment></RestfulServerTest_Comment>';
- $response = Director::test($url, null, null, 'PUT', $body);
- $this->assertEquals($response->getStatusCode(), 200); // Updated
- $this->assertEquals($response->getHeader('Location'), Controller::join_links(Director::absoluteBaseURL(), $url));
- $obj = Convert::xml2array($response->getBody());
- $this->assertEquals($obj['ID'], $comment1->ID);
- $this->assertEquals($obj['Comment'], 'updated');
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testHTTPAcceptAndContentType() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
-
- $headers = array('Accept' => 'application/json');
- $response = Director::test($url, null, null, 'GET', null, $headers);
- $this->assertEquals($response->getStatusCode(), 200); // Success
- $obj = Convert::json2obj($response->getBody());
- $this->assertEquals($obj->ID, $comment1->ID);
- $this->assertEquals($response->getHeader('Content-Type'), 'application/json');
- }
-
- public function testNotFound(){
- $_SERVER['PHP_AUTH_USER'] = 'user@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'user';
-
- $url = "/api/v1/RestfulServerTest_Comment/99";
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 404);
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testMethodNotAllowed() {
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
-
- $url = "/api/v1/RestfulServerTest_Comment/" . $comment1->ID;
- $response = Director::test($url, null, null, 'UNKNOWNHTTPMETHOD');
- $this->assertEquals($response->getStatusCode(), 405);
- }
-
- public function testConflictOnExistingResourceWhenUsingPost() {
- $rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating', 'rating1');
-
- $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID;
- $response = Director::test($url, null, null, 'POST');
- $this->assertEquals($response->getStatusCode(), 409);
- }
-
- public function testUnsupportedMediaType() {
- $_SERVER['PHP_AUTH_USER'] = 'user@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'user';
-
- $url = "/api/v1/RestfulServerTest_Comment";
- $data = "Comment||\/||updated"; // weird format
- $headers = array('Content-Type' => 'text/weirdformat');
- $response = Director::test($url, null, null, 'POST', $data, $headers);
- $this->assertEquals($response->getStatusCode(), 415);
-
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
- public function testXMLValueFormatting() {
- $rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating','rating1');
-
- $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertContains('<ID>' . $rating1->ID . '</ID>', $response->getBody());
- $this->assertContains('<Rating>' . $rating1->Rating . '</Rating>', $response->getBody());
- }
-
- public function testApiAccessFieldRestrictions() {
- $author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
- $rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating','rating1');
-
- $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertContains('<ID>', $response->getBody());
- $this->assertContains('<Rating>', $response->getBody());
- $this->assertContains('<Author', $response->getBody());
- $this->assertNotContains('<SecretField>', $response->getBody());
- $this->assertNotContains('<SecretRelation>', $response->getBody());
-
- $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID . '?add_fields=SecretField,SecretRelation';
- $response = Director::test($url, null, null, 'GET');
- $this->assertNotContains('<SecretField>', $response->getBody(),
- '"add_fields" URL parameter filters out disallowed fields from $api_access'
- );
- $this->assertNotContains('<SecretRelation>', $response->getBody(),
- '"add_fields" URL parameter filters out disallowed relations from $api_access'
- );
-
- $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID . '?fields=SecretField,SecretRelation';
- $response = Director::test($url, null, null, 'GET');
- $this->assertNotContains('<SecretField>', $response->getBody(),
- '"fields" URL parameter filters out disallowed fields from $api_access'
- );
- $this->assertNotContains('<SecretRelation>', $response->getBody(),
- '"fields" URL parameter filters out disallowed relations from $api_access'
- );
-
- $url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . '/Ratings';
- $response = Director::test($url, null, null, 'GET');
- $this->assertContains('<Rating>', $response->getBody(),
- 'Relation viewer shows fields allowed through $api_access'
- );
- $this->assertNotContains('<SecretField>', $response->getBody(),
- 'Relation viewer on has-many filters out disallowed fields from $api_access'
- );
- }
-
- public function testApiAccessRelationRestrictionsInline() {
- $author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
-
- $url = "/api/v1/RestfulServerTest_Author/" . $author1->ID;
- $response = Director::test($url, null, null, 'GET');
- $this->assertNotContains('<RelatedPages', $response->getBody(), 'Restricts many-many with api_access=false');
- $this->assertNotContains('<PublishedPages', $response->getBody(), 'Restricts has-many with api_access=false');
- }
-
- public function testApiAccessRelationRestrictionsOnEndpoint() {
- $author1 = $this->objFromFixture('RestfulServerTest_Author','author1');
-
- $url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . "/ProfilePage";
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals(404, $response->getStatusCode(), 'Restricts has-one with api_access=false');
-
- $url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . "/RelatedPages";
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals(404, $response->getStatusCode(), 'Restricts many-many with api_access=false');
-
- $url = "/api/v1/RestfulServerTest_Author/" . $author1->ID . "/PublishedPages";
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals(404, $response->getStatusCode(), 'Restricts has-many with api_access=false');
- }
-
- public function testApiAccessWithPUT() {
- $rating1 = $this->objFromFixture('RestfulServerTest_AuthorRating','rating1');
-
- $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID;
- $data = array(
- 'Rating' => '42',
- 'WriteProtectedField' => 'haxx0red'
- );
- $response = Director::test($url, $data, null, 'PUT');
- // Assumption: XML is default output
- $responseArr = Convert::xml2array($response->getBody());
- $this->assertEquals($responseArr['Rating'], 42);
- $this->assertNotEquals($responseArr['WriteProtectedField'], 'haxx0red');
- }
-
- public function testJSONDataFormatter() {
- $formatter = new JSONDataFormatter();
- $editor = $this->objFromFixture('Member', 'editor');
- $user = $this->objFromFixture('Member', 'user');
-
- $this->assertEquals(
- $formatter->convertDataObject($editor, array("FirstName", "Email")),
- '{"FirstName":"Editor","Email":"editor@test.com"}',
- "Correct JSON formatting with field subset");
-
- $set = DataObject::get(
- "Member",
- sprintf('"Member"."ID" IN (%s)', implode(',', array($editor->ID, $user->ID))),
- '"Email" ASC' // for sorting for postgres
- );
- $this->assertEquals(
- $formatter->convertDataObjectSet($set, array("FirstName", "Email")),
- '{"totalSize":null,"items":[{"FirstName":"Editor","Email":"editor@test.com"},{"FirstName":"User","Email":"user@test.com"}]}',
- "Correct JSON formatting on a dataobjectset with field filter");
- }
-
- public function testApiAccessWithPOST() {
- $url = "/api/v1/RestfulServerTest_AuthorRating";
- $data = array(
- 'Rating' => '42',
- 'WriteProtectedField' => 'haxx0red'
- );
- $response = Director::test($url, $data, null, 'POST');
- // Assumption: XML is default output
- $responseArr = Convert::xml2array($response->getBody());
- $this->assertEquals($responseArr['Rating'], 42);
- $this->assertNotEquals($responseArr['WriteProtectedField'], 'haxx0red');
- }
-
-}
-
-/**
- * Everybody can view comments, logged in members in the "users" group can create comments,
- * but only "editors" can edit or delete them.
- *
- */
-class RestfulServerTest_Comment extends DataObject implements PermissionProvider,TestOnly {
-
- static $api_access = true;
-
- static $db = array(
- "Name" => "Varchar(255)",
- "Comment" => "Text"
- );
-
- static $has_one = array(
- 'Page' => 'RestfulServerTest_Page',
- 'Author' => 'RestfulServerTest_Author',
- );
-
- public function providePermissions(){
- return array(
- 'EDIT_Comment' => 'Edit Comment Objects',
- 'CREATE_Comment' => 'Create Comment Objects',
- 'DELETE_Comment' => 'Delete Comment Objects',
- );
- }
-
- public function canView($member = null) {
- return true;
- }
-
- public function canEdit($member = null) {
- return Permission::checkMember($member, 'EDIT_Comment');
- }
-
- public function canDelete($member = null) {
- return Permission::checkMember($member, 'DELETE_Comment');
- }
-
- public function canCreate($member = null) {
- return Permission::checkMember($member, 'CREATE_Comment');
- }
-
-}
-
-class RestfulServerTest_SecretThing extends DataObject implements TestOnly,PermissionProvider{
- static $api_access = true;
-
- static $db = array(
- "Name" => "Varchar(255)",
- );
-
- public function canView($member = null) {
- return Permission::checkMember($member, 'VIEW_SecretThing');
- }
-
- public function providePermissions(){
- return array(
- 'VIEW_SecretThing' => 'View Secret Things',
- );
- }
-}
-
-class RestfulServerTest_Page extends DataObject implements TestOnly {
-
- static $api_access = false;
-
- static $db = array(
- 'Title' => 'Text',
- 'Content' => 'HTMLText',
- );
-
- static $has_one = array(
- 'Author' => 'RestfulServerTest_Author',
- );
-
- static $has_many = array(
- 'TestComments' => 'RestfulServerTest_Comment'
- );
-
- static $belongs_many_many = array(
- 'RelatedAuthors' => 'RestfulServerTest_Author',
- );
-
-}
-
-class RestfulServerTest_Author extends DataObject implements TestOnly {
-
- static $api_access = true;
-
- static $db = array(
- 'Name' => 'Text',
- );
-
- static $many_many = array(
- 'RelatedPages' => 'RestfulServerTest_Page',
- 'RelatedAuthors' => 'RestfulServerTest_Author',
- );
-
- static $has_many = array(
- 'PublishedPages' => 'RestfulServerTest_Page',
- 'Ratings' => 'RestfulServerTest_AuthorRating',
- );
-
- public function canView($member = null) {
- return true;
- }
-}
-
-class RestfulServerTest_AuthorRating extends DataObject implements TestOnly {
- static $api_access = array(
- 'view' => array(
- 'Rating',
- 'WriteProtectedField',
- 'Author'
- ),
- 'edit' => array(
- 'Rating'
- )
- );
-
- static $db = array(
- 'Rating' => 'Int',
- 'SecretField' => 'Text',
- 'WriteProtectedField' => 'Text',
- );
-
- static $has_one = array(
- 'Author' => 'RestfulServerTest_Author',
- 'SecretRelation' => 'RestfulServerTest_Author',
- );
-
- public function canView($member = null) {
- return true;
- }
-
- public function canEdit($member = null) {
- return true;
- }
-
- public function canCreate($member = null) {
- return true;
- }
-}
-
66 tests/api/RestfulServerTest.yml
View
@@ -1,66 +0,0 @@
-Member:
- editor:
- FirstName: Editor
- Email: editor@test.com
- Password: editor
- user:
- FirstName: User
- Email: user@test.com
- Password: user
-Group:
- editorgroup:
- Title: Editors
- Code: editors
- Members: =>Member.editor
- usergroup:
- Title: Users
- Code: users
- Members: =>Member.user
-Permission:
- perm1:
- Code: CREATE_Comment
- Group: =>Group.usergroup
- perm3:
- Code: EDIT_Comment
- Group: =>Group.editorgroup
- perm4:
- Code: DELETE_Comment
- Group: =>Group.editorgroup
- perm5:
- Code: CREATE_Comment
- Group: =>Group.editorgroup
- perm6:
- Code: VIEW_SecretThing
- Group: =>Group.editorgroup
-RestfulServerTest_Page:
- page1:
- Title: Testpage without API Access
-RestfulServerTest_Comment:
- comment1:
- Name: Joe
- Comment: This is a test comment
- Page: =>RestfulServerTest_Page.page1
-RestfulServerTest_Author:
- author1:
- FirstName: Author 1
- author2:
- FirstName: Author 2
- author3:
- Firstname: Author 3
- author4:
- FirstName: Author 4
- RelatedAuthors: =>RestfulServerTest_Author.author2,=>RestfulServerTest_Author.author3
-RestfulServerTest_AuthorRating:
- rating1:
- Rating: 3
- WriteProtectedField: Dont overwrite me
- SecretField: Dont look at me!
- Author: =>RestfulServerTest_Author.author1
- SecretRelation: =>RestfulServerTest_Author.author1
- rating2:
- Rating: 5
- Author: =>RestfulServerTest_Author.author1
- SecretRelation: =>RestfulServerTest_Author.author1
-RestfulServerTest_SecretThing:
- thing1:
- Name: Unspeakable
2  tests/model/DataExtensionTest.php
View
@@ -15,8 +15,6 @@ class DataExtensionTest extends SapphireTest {
);
function testOneToManyAssociationWithExtension() {
- // Fails in RestfulServerTest
- // Error: Object::__call() Method 'RelatedObjects' not found in class 'RestfulServerTest_Comment'
$contact = new DataExtensionTest_Member();
$contact->Website = "http://www.example.com";
Please sign in to comment.
Something went wrong with that request. Please try again.