View source
<?php
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;
}
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;
}
function restws_restws_resource_info() {
foreach (entity_get_info() as $entity_type => $info) {
$result[$entity_type] = array(
'label' => $info['label'],
'class' => 'RestWSEntityResourceController',
);
}
return $result;
}
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];
}
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;
}
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];
}
function restws_handle_request($op, $format, $resource_name, $id = NULL, $payload = NULL) {
$message = $status_message = '';
if ($resource = restws_resource_controller($resource_name)) {
$request = array(
'op' => &$op,
'format' => &$format,
'resource' => &$resource,
'id' => &$id,
'payload' => &$payload,
);
drupal_alter('restws_request', $request);
$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);
$status_message = '201 Created';
}
elseif ($op == 'query') {
if (!$resource instanceof RestWSQueryResourceControllerInterface) {
throw new RestWSException('Querying not available for this resource', 501);
}
print $format
->{$method}($resource, $payload);
}
else {
print $format
->{$method}($resource, $id, $payload);
}
drupal_add_http_header('Content-Type', $format
->mimeType());
} catch (RestWSException $e) {
$message = check_plain($e
->getHTTPError()) . ': ' . check_plain($e
->getMessage());
$status_message = $e
->getHTTPError();
}
}
else {
$status_message = $message = '403 Forbidden';
watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
}
}
else {
$status_message = $message = '404 Not Found';
}
restws_terminate_request($status_message, $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';
}
}
}
function restws_init() {
_restws_determine_router_item();
}
function _restws_determine_router_item() {
if (strpos(request_path(), '.') === FALSE) {
return;
}
$menu_paths = array();
foreach (restws_get_resource_info() as $resource => $info) {
$menu_paths[] = isset($info['menu_path']) ? $info['menu_path'] : $resource;
}
$formats = array_keys(restws_get_format_info());
$pattern = '#^((?:';
$pattern .= implode('|', $menu_paths);
$pattern .= ')\\/[1-9][0-9]*)\\.(?:';
$pattern .= implode('|', $formats);
$pattern .= ')$#i';
$count = 0;
$path = preg_replace($pattern, '\\1', request_path(), 1, $count);
if ($count && !menu_get_item()) {
$router_item = menu_get_item($path);
menu_set_item(NULL, $router_item);
}
}
function restws_menu_alter(&$items) {
foreach (restws_get_resource_info() as $resource => $info) {
$menu_path = isset($info['menu_path']) ? $info['menu_path'] . '/%' : $resource . '/%';
if (isset($items[$menu_path])) {
array_unshift($items[$menu_path]['page arguments'], $resource, $items[$menu_path]['page callback']);
$items[$menu_path]['page callback'] = 'restws_page_callback';
}
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,
'drupal_not_found',
),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
}
$menu_path = isset($info['menu_path']) ? substr($menu_path, 0, strlen($menu_path) - 2) : $resource;
if (isset($items[$menu_path])) {
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,
'drupal_not_found',
),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
}
foreach (array_keys(restws_get_format_info()) as $format) {
if (isset($items["{$menu_path}.{$format}"])) {
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,
'drupal_not_found',
),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
}
}
}
}
function restws_page_callback($resource, $page_callback) {
$resource_info = restws_get_resource_info($resource);
$resource_pos = isset($resource_info['menu_path']) ? count(explode('/', $resource_info['menu_path'])) - 1 : 0;
$id_arg = arg($resource_pos + 1);
$resource_arg = arg($resource_pos);
$format = FALSE;
$id = NULL;
if ($_SERVER['REQUEST_METHOD'] == 'GET' && ($pos = strrpos($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 = strrpos($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':
$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) {
$format = restws_format('json');
}
break;
default:
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === FALSE) {
$parts = explode(',', $_SERVER['HTTP_ACCEPT'], 2);
$format = restws_format_mimetype($parts[0]);
}
if ($format && !isset($_COOKIE[session_name()]) && variable_get('cache')) {
drupal_goto($_GET['q'] . '.' . $format
->getName(), array(
'query' => drupal_get_query_parameters(),
), 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';
}
}
if (!in_array($_SERVER['REQUEST_METHOD'], array(
'GET',
'HEAD',
'OPTIONS',
'TRACE',
)) && !restws_csrf_validation()) {
restws_terminate_request('403 Forbidden', '403 Access Denied: CSRF validation failed');
}
$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);
}
$args = func_get_args();
return call_user_func_array($page_callback, array_slice($args, 2));
}
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}" : '';
$base_options = array(
'absolute' => TRUE,
'alias' => TRUE,
);
$options += $base_options;
return url($basepath . $sub_path, $options);
}
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;
}
function restws_permission() {
$permissions = array();
foreach (restws_get_resource_info() as $type => $info) {
$permissions['access resource ' . $type] = array(
'title' => t('Access the resource %resource', array(
'%resource' => $type,
)),
);
}
return $permissions;
}
function restws_module_implements_alter(&$implementations, $hook) {
if ($hook == 'menu_alter') {
$group = $implementations['restws'];
unset($implementations['restws']);
$implementations['restws'] = $group;
}
}
function restws_meta_controls() {
$controls = array(
'sort' => 'sort',
'direction' => 'direction',
'page' => 'page',
'limit' => 'limit',
'full' => 'full',
);
drupal_alter('restws_meta_controls', $controls);
return $controls;
}
function restws_csrf_validation() {
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;
}
function restws_menu() {
$items['restws/session/token'] = array(
'page callback' => 'restws_session_token',
'access callback' => 'user_is_logged_in',
'type' => MENU_CALLBACK,
);
return $items;
}
function restws_session_token() {
drupal_add_http_header('Content-Type', 'text/plain');
print drupal_get_token('restws');
drupal_exit();
}
function restws_terminate_request($status_message = NULL, $message = NULL) {
if (!empty($message)) {
echo $message;
}
if (!empty($status_message)) {
drupal_add_http_header('Status', $status_message);
}
drupal_page_footer();
exit;
}