Mercurial > hg > rr-repo
view sites/all/modules/restws/restws.module @ 13:134d4b2e75f6
updated quicktabs and google analytics modules
author | danieleb <danielebarchiesi@me.com> |
---|---|
date | Tue, 29 Oct 2013 13:48:59 +0000 |
parents | ce11bbd8f642 |
children |
line wrap: on
line source
<?php /** * @file * RESTful web services module. */ /** * Returns info about all defined resources. * * @param string $resource * By default null, else the info for the given resource will be returned. */ function restws_get_resource_info($resource = NULL) { $info = &drupal_static(__FUNCTION__); if (!isset($info)) { $info = module_invoke_all('restws_resource_info'); drupal_alter('restws_resource_info', $info); } if (!empty($resource)) { return $info[$resource]; } return $info; } /** * Returns info about all defined formats. */ function restws_get_format_info() { $info = &drupal_static(__FUNCTION__); if (!isset($info)) { $info = module_invoke_all('restws_format_info'); drupal_alter('restws_format_info', $info); } return $info; } /** * Implements hook_restws_resource_info(). * * Provides resources for all entity types. */ function restws_restws_resource_info() { foreach (entity_get_info() as $entity_type => $info) { $result[$entity_type] = array( 'label' => $info['label'], 'class' => 'RestWSEntityResourceController', ); } return $result; } /** * Returns a instance of a resource controller. * * @return RestWSResourceControllerInterface * A resource controller object. */ function restws_resource_controller($name) { $static = &drupal_static(__FUNCTION__); if (!isset($static[$name])) { $info = restws_get_resource_info(); $static[$name] = isset($info[$name]) ? new $info[$name]['class']($name, $info[$name]) : FALSE; } return $static[$name]; } /** * Implements hook_restws_format_info(). * * Provides basic formats. */ function restws_restws_format_info() { $result = array( 'json' => array( 'label' => t('JSON'), 'class' => 'RestWSFormatJSON', 'mime type' => 'application/json', ), 'xml' => array( 'label' => t('XML'), 'class' => 'RestWSFormatXML', 'mime type' => 'application/xml', ), ); if (module_exists('rdf')) { $result['rdf'] = array( 'label' => t('RDF'), 'class' => 'RestWSFormatRDF', 'mime type' => 'application/rdf+xml', ); } return $result; } /** * Returns an instance of a format. * * @return RestWSFormatInterface * A resource format object. */ function restws_format($name) { $static = &drupal_static(__FUNCTION__); if (!isset($static[$name])) { $info = restws_get_format_info(); $static[$name] = isset($info[$name]) ? new $info[$name]['class']($name, $info[$name]) : FALSE; } return $static[$name]; } /** * Handles a request. * * @param string $op * One of 'create', 'update', 'delete' or 'view'. */ function restws_handle_request($op, $format, $resource_name, $id = NULL, $payload = NULL) { if ($resource = restws_resource_controller($resource_name)) { // Allow other modules to change the web service request or react upon it. $request = array( 'op' => &$op, 'format' => &$format, 'resource' => &$resource, 'id' => &$id, 'payload' => &$payload, ); drupal_alter('restws_request', $request); // Since there is no access callback for query we need to use view. $access_op = $op == 'query' ? 'view' : $op; if (user_access('access resource ' . $resource_name) && $resource->access($access_op, $id)) { try { $method = $op . 'Resource'; if ($op == 'create') { print $format->$method($resource, $payload); drupal_add_http_header('Status', '201 Created'); } elseif ($op == 'query') { if (!$resource instanceof RestWSQueryResourceControllerInterface) { throw new RestWSException('Quering not available for this resources', 501); } print $format->$method($resource, $payload); } else { print $format->$method($resource, $id, $payload); } drupal_add_http_header('Content-Type', $format->mimeType()); } catch (RestWSException $e) { echo check_plain($e->getHTTPError()) . ': ' . check_plain($e->getMessage()); drupal_add_http_header('Status', $e->getHTTPError()); } } else { echo '403 Forbidden'; drupal_add_http_header('Status', '403 Forbidden'); watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); } } else { echo '404 Not Found'; drupal_add_http_header('Status', '404 Not Found'); } drupal_page_footer(); exit; } /** * An exception defining the HTTP error code and message. */ class RestWSException extends Exception { public function getHTTPError() { $code = $this->getCode(); switch ($code) { case 403: return '403 Forbidden'; case 404: return '404 Not Found'; case 406: return '406 Not Acceptable'; case 412: return '412 Precondition Failed'; case 422: return '422 Unprocessable Entity'; default: return '500 Internal Server Error'; } } } /** * Implements hook_menu_alter(). */ function restws_menu_alter(&$items) { foreach (restws_get_resource_info() as $resource => $info) { // Resource full path (e.g. /node/% or /user/%) for accessing specific // resources. $menu_path = isset($info['menu_path']) ? $info['menu_path'] . '/%' : $resource . '/%'; // Replace existing page callbacks with our own (e.g. node/%) if (isset($items[$menu_path])) { // Prepend the page callback and the resource to the page arguments. // So we can re-use it on standard HTML page requests. array_unshift($items[$menu_path]['page arguments'], $resource, $items[$menu_path]['page callback']); $items[$menu_path]['page callback'] = 'restws_page_callback'; } // Also replace wildcard loaders (e.g. node/%node) elseif (isset($items[$menu_path . $resource])) { $menu_path = $menu_path . $resource; array_unshift($items[$menu_path]['page arguments'], $resource, $items[$menu_path]['page callback']); $items[$menu_path]['page callback'] = 'restws_page_callback'; } else { $items[$menu_path] = array( 'page callback' => 'restws_page_callback', 'page arguments' => array($resource), 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); } // Resource base path (e.g. /node or /user) for creating resources. $menu_path = isset($info['menu_path']) ? substr($menu_path, 0, strlen($menu_path) - 2) : $resource; if (isset($items[$menu_path])) { // Prepend the page callback and the resource to the page arguments. if (!isset($items[$menu_path]['page arguments'])) { $items[$menu_path]['page arguments'] = array(); } array_unshift($items[$menu_path]['page arguments'], $resource, $items[$menu_path]['page callback']); $items[$menu_path]['page callback'] = 'restws_page_callback'; } else { $items[$menu_path] = array( 'page callback' => 'restws_page_callback', 'page arguments' => array($resource), 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); } // Querying menu paths. foreach (array_keys(restws_get_format_info()) as $format) { // Resource base path URLs with the suffixes (e.g. node.json or user.xml) // for querying. if (isset($items["$menu_path.$format"])) { // Prepend the page callback and the resource to the page arguments. if (!isset($items["$menu_path.$format"]['page arguments'])) { $items["$menu_path.$format"]['page arguments'] = array(); } array_unshift($items["$menu_path.$format"]['page arguments'], $resource, $items["$menu_path.$format"]['page callback']); $items["$menu_path.$format"]['page callback'] = 'restws_page_callback'; } else { $items["$menu_path.$format"] = array( 'page callback' => 'restws_page_callback', 'page arguments' => array($resource), 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); } } } } /** * Menu page callback. */ function restws_page_callback($resource, $page_callback = NULL) { $id_arg = arg(1); $resource_arg = arg(0); $format = FALSE; $id = NULL; // Check for an appended .format string on GET requests only to avoid CSRF // attacks on POST requests. if ($_SERVER['REQUEST_METHOD'] == 'GET' && ($pos = strpos($id_arg, '.')) && $format_name = substr($id_arg, $pos + 1)) { $id = substr($id_arg, 0, $pos); $format = restws_format($format_name); } elseif ($_SERVER['REQUEST_METHOD'] == 'GET' && ($pos = strpos($resource_arg, '.')) && $format_name = substr($resource_arg, $pos + 1)) { $format = restws_format($format_name); } else { $id = $id_arg; switch ($_SERVER['REQUEST_METHOD']) { case 'POST': case 'PUT': // Get format MIME type form HTTP Content type header. $parts = explode(';', $_SERVER['CONTENT_TYPE'], 2); $format = restws_format_mimetype($parts[0]); break; case 'DELETE': if (isset($_SERVER['HTTP_ACCEPT'])) { $parts = explode(',', $_SERVER['HTTP_ACCEPT'], 2); $format = restws_format_mimetype($parts[0]); } if (!$format) { // We don't care about the format, just pick JSON. $format = restws_format('json'); } break; default: // Get the format MIME type form the HTTP Accept header. // Ignore requests from web browsers that accept HTML. if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === FALSE) { // Use the first MIME type. $parts = explode(',', $_SERVER['HTTP_ACCEPT'], 2); $format = restws_format_mimetype($parts[0]); } // Consumers should not use this URL if page caching is enabled. // Drupal's page cache IDs are only determined by URL path, so this // could poison the HTML page cache. A browser request to /node/1 could // suddenly return JSON if the cache was primed with this RESTWS // response. if ($format && !isset($_COOKIE[session_name()]) && variable_get('cache')) { // Redirect to the URL path containing the format name instead. drupal_goto($_GET['q'] . '.' . $format->getName(), array(), 301); } } } if ($format) { switch ($_SERVER['REQUEST_METHOD']) { case 'POST': $op = 'create'; break; case 'PUT': $op = 'update'; break; case 'DELETE': $op = 'delete'; break; default: if (!empty($id)) { $op = 'view'; } else { $op = 'query'; } } // CSRF protection on write operations. if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD', 'OPTIONS', 'TRACE')) && !restws_csrf_validation()) { echo '403 Access Denied: CSRF validation failed'; drupal_add_http_header('Status', '403 Forbidden'); drupal_page_footer(); exit; } $payload = file_get_contents('php://input'); if ($file = variable_get('restws_debug_log')) { $log = date(DATE_ISO8601) . "\n"; $log .= 'Resource: ' . $resource . "\n"; $log .= 'Operation: ' . $op . "\n"; $log .= 'Format: ' . $format->mimeType() . "\n"; $log .= 'Id: ' . $id . "\n"; $log .= 'Payload: ' . $payload . "\n"; $log .= "----------------------------------------------------------------\n"; file_put_contents($file, $log, FILE_APPEND); } restws_handle_request($op, $format, $resource, $id, $payload); } // @todo: Determine human readable URIs and redirect, if there is no // page callback. if (isset($page_callback)) { // Further page callback arguments have been appended to our arguments. $args = func_get_args(); return call_user_func_array($page_callback, array_slice($args, 2)); } echo '404 Not Found'; drupal_add_http_header('Status', '404 Not Found'); drupal_page_footer(); exit; } /** * Returns the URI used for the given resource. * * @param string $resource * The resource for which the URI should be returned. * @param int $id * The resource ID or NULL if only the base path should be returned. * @param array $options * Optional array that is passed to url(). */ function restws_resource_uri($resource, $id = NULL, array $options = array()) { $info = restws_get_resource_info($resource); $basepath = isset($info['menu_path']) ? $info['menu_path'] : $resource; $sub_path = isset($id) ? "/$id" : ''; // Avoid having the URLs aliased. $base_options = array('absolute' => TRUE, 'alias' => TRUE); $options += $base_options; return url($basepath . $sub_path, $options); } /** * Returns the format instance for a given MIME type. * * @param string $mime * The MIME type, e.g. 'application/json' or 'application/xml'. * * @return bool|RestWSFormatInterface * The format controller or FALSE if the format was not found. */ function restws_format_mimetype($mime) { foreach (restws_get_format_info() as $format_name => $info) { if ($info['mime type'] == $mime) { return restws_format($format_name); } } return FALSE; } /** * Implements hook_permission(). */ function restws_permission() { $permissions = array(); // Create service access permissions per resource type. foreach (restws_get_resource_info() as $type => $info) { $permissions['access resource ' . $type] = array( 'title' => t('Access the resource %resource', array('%resource' => $type)), ); } return $permissions; } /** * Implements hook_module_implements_alter(). */ function restws_module_implements_alter(&$implementations, $hook) { // Make sure that restws runs last. // @todo remove entity_info_alter once https://drupal.org/node/1780646 is fixed. if ($hook == 'menu_alter' || $hook == 'entity_info_alter') { $group = $implementations['restws']; unset($implementations['restws']); $implementations['restws'] = $group; } } /** * Return all available meta controls. */ function restws_meta_controls() { return array( 'sort' => 'sort', 'direction' => 'direction', 'page' => 'page', 'limit' => 'limit', 'full' => 'full', ); } /** * Ensures that a request with cookies has the required CSRF header set. * * @return bool * TRUE if the request passed the CSRF protection, FALSE otherwise. */ function restws_csrf_validation() { // This check only applies if the user was successfully authenticated and the // request comes with a session cookie. if (user_is_logged_in() && !empty($_COOKIE[session_name()])) { return isset($_SERVER['HTTP_X_CSRF_TOKEN']) && drupal_valid_token($_SERVER['HTTP_X_CSRF_TOKEN'], 'restws'); } return TRUE; } /** * Implements hook_menu(). */ function restws_menu() { $items['restws/session/token'] = array( 'page callback' => 'restws_session_token', // Only authenticated users are allowed to retrieve a session token. 'access callback' => 'user_is_logged_in', 'type' => MENU_CALLBACK, ); return $items; } /** * Page callback: returns a session token for the currently active user. */ function restws_session_token() { drupal_add_http_header('Content-Type', 'text/plain'); print drupal_get_token('restws'); drupal_exit(); } /** * Access callback for the node entity. * * Replacement for entity_metadata_no_hook_node_access() because it does not * work with the create operation. * * @todo Remove this once https://drupal.org/node/1780646 is fixed. * * @see restws_entity_info_alter() * @see entity_metadata_no_hook_node_access() */ function restws_entity_node_access($op, $node = NULL, $account = NULL) { // First deal with the case where a $node is provided. if (isset($node)) { // Ugly hack to handle field access, because entity_api does not distinguish // between 'create' and 'update' permissions for fields. This should rather // be fixed in EntityStructureWrapper::propertyAccess() (entity.wrapper.inc). if ($op == 'update' && empty($node->nid)) { $op = 'create'; } if ($op == 'create') { if (isset($node->type)) { return node_access($op, $node->type, $account); } else { throw new EntityMalformedException('Permission to create a node was requested but no node type was given.'); } } // If a non-default revision is given, incorporate revision access. $default_revision = node_load($node->nid); if ($node->vid !== $default_revision->vid) { return _node_revision_access($node, $op, $account); } else { return node_access($op, $node, $account); } } // No node is provided. Check for access to all nodes. if (user_access('bypass node access', $account)) { return TRUE; } if (!user_access('access content', $account)) { return FALSE; } if ($op == 'view' && node_access_view_all_nodes($account)) { return TRUE; } return FALSE; } /** * Implements hook_entity_info_alter(). * * @todo Remove this once https://drupal.org/node/1780646 is fixed. */ function restws_entity_info_alter(&$info) { // In order for this to work we have to make sure we run after entity_api. // @see restws_module_implements_alter(). $info['node']['access callback'] = 'restws_entity_node_access'; }