diff --git a/code/BasicRestfulAuthenticator.php b/code/BasicRestfulAuthenticator.php
index ac9af7f..843a712 100644
--- a/code/BasicRestfulAuthenticator.php
+++ b/code/BasicRestfulAuthenticator.php
@@ -8,32 +8,34 @@
* application accessing the RestfulServer to store logins in plain text (or in
* decrytable form)
*/
-class BasicRestfulAuthenticator {
+class BasicRestfulAuthenticator
+{
+ /**
+ * The authenticate function
+ *
+ * Takes the basic auth details and attempts to log a user in from the DB
+ *
+ * @return Member|false The Member object, or false if no member
+ */
+ public static function authenticate()
+ {
+ //if there is no username or password, break
+ if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
+ return false;
+ }
- /**
- * The authenticate function
- *
- * Takes the basic auth details and attempts to log a user in from the DB
- *
- * @return Member|false The Member object, or false if no member
- */
- public static function authenticate() {
- //if there is no username or password, break
- if(!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) return false;
-
- //Attempt to authenticate with the default authenticator for the site
- $authClass = Authenticator::get_default_authenticator();
- $member = $authClass::authenticate(array(
- 'Email' => $_SERVER['PHP_AUTH_USER'],
- 'Password' => $_SERVER['PHP_AUTH_PW'],
- ));
-
- //Log the member in and return the member, if they were found
- if($member) {
- $member->LogIn(false);
- return $member;
- }
- return false;
- }
+ //Attempt to authenticate with the default authenticator for the site
+ $authClass = Authenticator::get_default_authenticator();
+ $member = $authClass::authenticate(array(
+ 'Email' => $_SERVER['PHP_AUTH_USER'],
+ 'Password' => $_SERVER['PHP_AUTH_PW'],
+ ));
+ //Log the member in and return the member, if they were found
+ if ($member) {
+ $member->LogIn(false);
+ return $member;
+ }
+ return false;
+ }
}
diff --git a/code/RestfulServer.php b/code/RestfulServer.php
index 4ecb8de..2462feb 100644
--- a/code/RestfulServer.php
+++ b/code/RestfulServer.php
@@ -26,560 +26,629 @@
* @package framework
* @subpackage api
*/
-class RestfulServer extends Controller {
- static $url_handlers = array(
- '$ClassName/$ID/$Relation' => 'handleAction'
- #'$ClassName/#ID' => 'handleItem',
- #'$ClassName' => 'handleList',
- );
+class RestfulServer extends Controller
+{
+ public static $url_handlers = array(
+ '$ClassName/$ID/$Relation' => 'handleAction'
+ #'$ClassName/#ID' => 'handleItem',
+ #'$ClassName' => 'handleList',
+ );
- protected static $api_base = "api/v1/";
+ protected static $api_base = "api/v1/";
- protected static $authenticator = 'BasicRestfulAuthenticator';
+ protected static $authenticator = 'BasicRestfulAuthenticator';
- /**
- * 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;
-
- public static $allowed_actions = array(
- 'index'
- );
-
- /*
- function handleItem($request) {
- return new RestfulServer_Item(DataObject::get_by_id($request->param("ClassName"), $request->param("ID")));
- }
+ /**
+ * 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;
+
+ public 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"),""));
- }
- */
+ function handleList($request) {
+ return new RestfulServer_List(DataObject::get($request->param("ClassName"),""));
+ }
+ */
- function init() {
- /* This sets up SiteTree the same as when viewing a page through the frontend. Versioned defaults
- * to Stage, and then when viewing the front-end Versioned::choose_site_stage changes it to Live.
- * TODO: In 3.2 we should make the default Live, then change to Stage in the admin area (with a nicer API)
- */
- if (class_exists('SiteTree')) singleton('SiteTree')->extend('modelascontrollerInit', $this);
- parent::init();
- }
+ public function init()
+ {
+ /* This sets up SiteTree the same as when viewing a page through the frontend. Versioned defaults
+ * to Stage, and then when viewing the front-end Versioned::choose_site_stage changes it to Live.
+ * TODO: In 3.2 we should make the default Live, then change to Stage in the admin area (with a nicer API)
+ */
+ if (class_exists('SiteTree')) {
+ singleton('SiteTree')->extend('modelascontrollerInit', $this);
+ }
+ parent::init();
+ }
- /**
- * 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();
+ /**
+ * This handler acts as the switchboard for the controller.
+ * Since no $Action url-param is set, all requests are sent here.
+ */
+ public 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();
+ // 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);
- }
+ // 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 ($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:
- *
- *
- * Value
- * ...
- *
- * ...
- *
- *
- *
- *
- * ...
- *
- *
- *
- *
- *
- *
- * 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//
- $obj = $this->getObjectQuery($className, $id, $params)->First();
- if(!$obj) return $this->notFound();
- if(!$obj->canView()) return $this->permissionFailure();
+ // if no HTTP verb matches, return error
+ return $this->methodNotAllowed();
+ }
+
+ /**
+ * Handler for object read.
+ *
+ * The data object will be returned in the following format:
+ *
+ *
+ * Value
+ * ...
+ *
+ * ...
+ *
+ *
+ *
+ *
+ * ...
+ *
+ *
+ *
+ *
+ *
+ *
+ * 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//
+ $obj = $this->getObjectQuery($className, $id, $params)->First();
+ if (!$obj) {
+ return $this->notFound();
+ }
+ if (!$obj->canView()) {
+ return $this->permissionFailure();
+ }
- // Format: /api/v1///
- 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/
- $obj = $this->getObjectsQuery($className, $params, $sort, $limit);
- }
-
- $this->getResponse()->addHeader('Content-Type', $responseFormatter->getOutputContentType());
-
- $rawFields = $this->request->getVar('fields');
- $fields = $rawFields ? explode(',', $rawFields) : null;
+ // Format: /api/v1///
+ 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/
+ $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());
- $objs = new ArrayList($obj->toArray());
- foreach($objs as $obj) if(!$obj->canView()) $objs->remove($obj);
- return $responseFormatter->convertDataObjectSet($objs, $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'];
+ if ($obj instanceof SS_List) {
+ $responseFormatter->setTotalSize($obj->dataQuery()->query()->unlimitedRowCount());
+ $objs = new ArrayList($obj->toArray());
+ foreach ($objs as $obj) {
+ if (!$obj->canView()) {
+ $objs->remove($obj);
+ }
+ }
+ return $responseFormatter->convertDataObjectSet($objs, $fields);
+ } elseif (!$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);
- }
+ // 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']);
- }
-
- }
+ 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;
- }
+ // 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());
+ /**
+ * 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]}";
- }
+ // 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);
- }
+ $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());
+ /**
+ * 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]}";
- }
+ // 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']));
- }
+ $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//
- *
- * @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.";
- }
+ $obj->update($data);
+ $obj->write();
+
+ return $obj;
+ }
+
+ /**
+ * Gets a single DataObject by ID,
+ * through a request like /api/v1//
+ *
+ * @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";
- }
-
- /**
- * A function to authenticate a user
- *
- * @return Member|false the logged in member
- */
- protected function authenticate() {
- $authClass = self::config()->authenticator;
- return $authClass::authenticate();
- }
-
- /**
- * 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;
- }
-
+ 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";
+ }
+
+ /**
+ * A function to authenticate a user
+ *
+ * @return Member|false the logged in member
+ */
+ protected function authenticate()
+ {
+ $authClass = self::config()->authenticator;
+ return $authClass::authenticate();
+ }
+
+ /**
+ * 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;
+ }
}
/**
@@ -588,18 +657,21 @@ class RestfulServer extends Controller {
* @package framework
* @subpackage api
*/
-class RestfulServer_List {
- static $url_handlers = array(
- '#ID' => 'handleItem',
- );
+class RestfulServer_List
+{
+ public 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')));
- }
+ public function __construct($list)
+ {
+ $this->list = $list;
+ }
+
+ public function handleItem($request)
+ {
+ return new RestulServer_Item($this->list->getById($request->param('ID')));
+ }
}
/**
@@ -608,20 +680,26 @@ class RestfulServer_List {
* @package framework
* @subpackage api
*/
-class RestfulServer_Item {
- static $url_handlers = array(
- '$Relation' => 'handleRelation',
- );
+class RestfulServer_Item
+{
+ public static $url_handlers = array(
+ '$Relation' => 'handleRelation',
+ );
- function __construct($item) {
- $this->item = $item;
- }
-
- function handleRelation($request) {
- $funcName = $request('Relation');
- $relation = $this->item->$funcName();
+ public function __construct($item)
+ {
+ $this->item = $item;
+ }
+
+ public 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);
- }
+ if ($relation instanceof SS_List) {
+ return new RestfulServer_List($relation);
+ } else {
+ return new RestfulServer_Item($relation);
+ }
+ }
}
diff --git a/tests/travis/_config.php b/tests/travis/_config.php
index 3508957..e226259 100644
--- a/tests/travis/_config.php
+++ b/tests/travis/_config.php
@@ -19,4 +19,6 @@ MySQLDatabase::set_connection_charset('utf8');
SSViewer::set_theme('simple');
// Enable nested URLs for this site (e.g. page/sub-page/)
-if(class_exists('SiteTree')) SiteTree::enable_nested_urls();
\ No newline at end of file
+if (class_exists('SiteTree')) {
+ SiteTree::enable_nested_urls();
+}
diff --git a/tests/travis/_ss_environment.php b/tests/travis/_ss_environment.php
index eb38829..653c416 100644
--- a/tests/travis/_ss_environment.php
+++ b/tests/travis/_ss_environment.php
@@ -5,33 +5,33 @@ define('SS_ENVIRONMENT_TYPE', 'dev');
/* Database connection */
$db = getenv('TESTDB');
-switch($db) {
+switch ($db) {
case "PGSQL";
- define('SS_DATABASE_CLASS', 'PostgreSQLDatabase');
- define('SS_DATABASE_USERNAME', 'postgres');
- define('SS_DATABASE_PASSWORD', '');
- break;
+ define('SS_DATABASE_CLASS', 'PostgreSQLDatabase');
+ define('SS_DATABASE_USERNAME', 'postgres');
+ define('SS_DATABASE_PASSWORD', '');
+ break;
case "MYSQL":
- define('SS_DATABASE_CLASS', 'MySQLDatabase');
- define('SS_DATABASE_USERNAME', 'root');
- define('SS_DATABASE_PASSWORD', '');
- break;
+ define('SS_DATABASE_CLASS', 'MySQLDatabase');
+ define('SS_DATABASE_USERNAME', 'root');
+ define('SS_DATABASE_PASSWORD', '');
+ break;
default:
- define('SS_DATABASE_CLASS', 'SQLitePDODatabase');
- define('SS_DATABASE_USERNAME', 'root');
- define('SS_DATABASE_PASSWORD', '');
+ define('SS_DATABASE_CLASS', 'SQLitePDODatabase');
+ define('SS_DATABASE_USERNAME', 'root');
+ define('SS_DATABASE_PASSWORD', '');
}
echo SS_DATABASE_CLASS;
-define('SS_DATABASE_SERVER', 'localhost');
+define('SS_DATABASE_SERVER', 'localhost');
define('SS_DATABASE_CHOOSE_NAME', true);
-/* Configure a default username and password to access the CMS on all sites in this environment. */
-define('SS_DEFAULT_ADMIN_USERNAME', 'username');
+/* Configure a default username and password to access the CMS on all sites in this environment. */
+define('SS_DEFAULT_ADMIN_USERNAME', 'username');
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
$_FILE_TO_URL_MAPPING[dirname(__FILE__)] = 'http://localhost';
diff --git a/tests/unit/RestfulServerTest.php b/tests/unit/RestfulServerTest.php
index e5cf595..54a9d77 100644
--- a/tests/unit/RestfulServerTest.php
+++ b/tests/unit/RestfulServerTest.php
@@ -6,452 +6,474 @@
* @todo Test DELETE verb
*
*/
-class RestfulServerTest extends SapphireTest {
-
- static $fixture_file = 'RestfulServerTest.yml';
+class RestfulServerTest extends SapphireTest
+{
+ public static $fixture_file = 'RestfulServerTest.yml';
- protected $extraDataObjects = array(
- 'RestfulServerTest_Comment',
- 'RestfulServerTest_SecretThing',
- 'RestfulServerTest_Page',
- 'RestfulServerTest_Author',
- 'RestfulServerTest_AuthorRating',
- );
+ 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('', $response->getBody());
- $this->assertContains('', $response->getBody());
- $this->assertContains('', $response->getBody());
- $this->assertContains('getBody());
- $this->assertContains('getBody());
- }
-
- public function testAuthenticatedGET() {
- $thing1 = $this->objFromFixture('RestfulServerTest_SecretThing', 'thing1');
- $comment1 = $this->objFromFixture('RestfulServerTest_Comment', 'comment1');
+ 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('', $response->getBody());
+ $this->assertContains('', $response->getBody());
+ $this->assertContains('', $response->getBody());
+ $this->assertContains('getBody());
+ $this->assertContains('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);
- }
+ // @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
- 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 = 'updated';
- $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 = '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::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('' . $rating1->ID . '', $response->getBody());
- $this->assertContains('' . $rating1->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('', $response->getBody());
- $this->assertContains('', $response->getBody());
- $this->assertContains('getBody());
- $this->assertNotContains('', $response->getBody());
- $this->assertNotContains('', $response->getBody());
-
- $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID . '?add_fields=SecretField,SecretRelation';
- $response = Director::test($url, null, null, 'GET');
- $this->assertNotContains('', $response->getBody(),
- '"add_fields" URL parameter filters out disallowed fields from $api_access'
- );
- $this->assertNotContains('', $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('', $response->getBody(),
- '"fields" URL parameter filters out disallowed fields from $api_access'
- );
- $this->assertNotContains('', $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('', $response->getBody(),
- 'Relation viewer shows fields allowed through $api_access'
- );
- $this->assertNotContains('', $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('getBody(), 'Restricts many-many with api_access=false');
- $this->assertNotContains('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');
- }
+ $_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
- public function testJSONDataFormatter() {
- $formatter = new JSONDataFormatter();
- $editor = $this->objFromFixture('Member', 'editor');
- $user = $this->objFromFixture('Member', 'user');
+ 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);
+ }
- $this->assertEquals(
- $formatter->convertDataObject($editor, array("FirstName", "Email")),
- '{"FirstName":"Editor","Email":"editor@test.com"}',
- "Correct JSON formatting with field subset");
+ 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 = 'updated';
+ $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 = '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::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('' . $rating1->ID . '', $response->getBody());
+ $this->assertContains('' . $rating1->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('', $response->getBody());
+ $this->assertContains('', $response->getBody());
+ $this->assertContains('getBody());
+ $this->assertNotContains('', $response->getBody());
+ $this->assertNotContains('', $response->getBody());
+
+ $url = "/api/v1/RestfulServerTest_AuthorRating/" . $rating1->ID . '?add_fields=SecretField,SecretRelation';
+ $response = Director::test($url, null, null, 'GET');
+ $this->assertNotContains('', $response->getBody(),
+ '"add_fields" URL parameter filters out disallowed fields from $api_access'
+ );
+ $this->assertNotContains('', $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('', $response->getBody(),
+ '"fields" URL parameter filters out disallowed fields from $api_access'
+ );
+ $this->assertNotContains('', $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('', $response->getBody(),
+ 'Relation viewer shows fields allowed through $api_access'
+ );
+ $this->assertNotContains('', $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('getBody(), 'Restricts many-many with api_access=false');
+ $this->assertNotContains('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');
+ }
- $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');
- }
+ public function testJSONDataFormatter()
+ {
+ $formatter = new JSONDataFormatter();
+ $editor = $this->objFromFixture('Member', 'editor');
+ $user = $this->objFromFixture('Member', 'user');
- public function testCanViewRespectedInList() {
- // Default content type
- $url = "/api/v1/RestfulServerTest_SecretThing/";
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 200);
- $this->assertNotContains('Unspeakable', $response->getBody());
+ $this->assertEquals(
+ $formatter->convertDataObject($editor, array("FirstName", "Email")),
+ '{"FirstName":"Editor","Email":"editor@test.com"}',
+ "Correct JSON formatting with field subset");
- // JSON content type
- $url = "/api/v1/RestfulServerTest_SecretThing.json";
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 200);
- $this->assertNotContains('Unspeakable', $response->getBody());
+ $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');
+ }
- // With authentication
- $_SERVER['PHP_AUTH_USER'] = 'editor@test.com';
- $_SERVER['PHP_AUTH_PW'] = 'editor';
- $url = "/api/v1/RestfulServerTest_SecretThing/";
- $response = Director::test($url, null, null, 'GET');
- $this->assertEquals($response->getStatusCode(), 200);
- $this->assertContains('Unspeakable', $response->getBody());
- unset($_SERVER['PHP_AUTH_USER']);
- unset($_SERVER['PHP_AUTH_PW']);
- }
-
+ public function testCanViewRespectedInList()
+ {
+ // Default content type
+ $url = "/api/v1/RestfulServerTest_SecretThing/";
+ $response = Director::test($url, null, null, 'GET');
+ $this->assertEquals($response->getStatusCode(), 200);
+ $this->assertNotContains('Unspeakable', $response->getBody());
+
+ // JSON content type
+ $url = "/api/v1/RestfulServerTest_SecretThing.json";
+ $response = Director::test($url, null, null, 'GET');
+ $this->assertEquals($response->getStatusCode(), 200);
+ $this->assertNotContains('Unspeakable', $response->getBody());
+
+ // With authentication
+ $_SERVER['PHP_AUTH_USER'] = 'editor@test.com';
+ $_SERVER['PHP_AUTH_PW'] = 'editor';
+ $url = "/api/v1/RestfulServerTest_SecretThing/";
+ $response = Director::test($url, null, null, 'GET');
+ $this->assertEquals($response->getStatusCode(), 200);
+ $this->assertContains('Unspeakable', $response->getBody());
+ unset($_SERVER['PHP_AUTH_USER']);
+ unset($_SERVER['PHP_AUTH_PW']);
+ }
}
/**
@@ -459,143 +481,153 @@ class RestfulServerTest extends SapphireTest {
* 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_Comment extends DataObject implements PermissionProvider,TestOnly
+{
+ public static $api_access = true;
+
+ public static $db = array(
+ "Name" => "Varchar(255)",
+ "Comment" => "Text"
+ );
+
+ public 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_SecretThing extends DataObject implements TestOnly,PermissionProvider
+{
+ public static $api_access = true;
+
+ public 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_Page extends DataObject implements TestOnly
+{
+ public static $api_access = false;
+
+ public static $db = array(
+ 'Title' => 'Text',
+ 'Content' => 'HTMLText',
+ );
+
+ public static $has_one = array(
+ 'Author' => 'RestfulServerTest_Author',
+ );
+
+ public static $has_many = array(
+ 'TestComments' => 'RestfulServerTest_Comment'
+ );
+
+ public 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_Author extends DataObject implements TestOnly
+{
+ public static $api_access = true;
+
+ public static $db = array(
+ 'Name' => 'Text',
+ );
+
+ public static $many_many = array(
+ 'RelatedPages' => 'RestfulServerTest_Page',
+ 'RelatedAuthors' => 'RestfulServerTest_Author',
+ );
+
+ public 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;
- }
+class RestfulServerTest_AuthorRating extends DataObject implements TestOnly
+{
+ public static $api_access = array(
+ 'view' => array(
+ 'Rating',
+ 'WriteProtectedField',
+ 'Author'
+ ),
+ 'edit' => array(
+ 'Rating'
+ )
+ );
+
+ public static $db = array(
+ 'Rating' => 'Int',
+ 'SecretField' => 'Text',
+ 'WriteProtectedField' => 'Text',
+ );
+
+ public 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;
+ }
}
-