View source
<?php
include_once __DIR__ . '/restful.entity.inc';
include_once __DIR__ . '/restful.cache.inc';
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Plugin\PluginBase;
use Drupal\restful\Exception\RestfulException;
use Drupal\restful\Http\HttpHeader;
use Drupal\restful\Http\RequestInterface;
use Drupal\restful\Http\ResponseInterface;
use Drupal\restful\Plugin\resource\Decorators\CacheDecoratedResource;
use Drupal\restful\Plugin\resource\Decorators\RateLimitDecoratedResource;
use Drupal\restful\Plugin\resource\ResourceInterface;
use Drupal\restful\RestfulManager;
function restful_menu() {
$base_path = variable_get('restful_hook_menu_base_path', 'api');
$items = array();
$plugins = restful()
->getResourceManager()
->getPlugins();
foreach ($plugins
->getIterator() as $plugin) {
if (!$plugin instanceof ResourceInterface) {
continue;
}
$plugin_definition = $plugin
->getPluginDefinition();
if (!$plugin_definition['hookMenu']) {
continue;
}
$item = array(
'title' => $plugin_definition['name'],
'access callback' => RestfulManager::FRONT_CONTROLLER_ACCESS_CALLBACK,
'access arguments' => array(
$plugin_definition['resource'],
),
'page callback' => RestfulManager::FRONT_CONTROLLER_CALLBACK,
'page arguments' => array(
$plugin_definition['resource'],
),
'delivery callback' => 'restful_delivery',
'type' => MENU_CALLBACK,
);
if (!isset($plugin_definition['menuItem'])) {
$item['access arguments'][] = 1;
$item['page arguments'][] = 1;
$items[$base_path . '/v' . $plugin_definition['majorVersion'] . '.' . $plugin_definition['minorVersion'] . '/' . $plugin_definition['resource']] = $item;
$items[$base_path . '/v' . $plugin_definition['majorVersion'] . '/' . $plugin_definition['resource']] = $item;
$item['access arguments'] = $item['page arguments'] = array(
1,
);
$items[$base_path . '/' . $plugin_definition['resource']] = $item;
}
else {
$path = implode('/', array(
$base_path,
$plugin_definition['menuItem'],
));
$path = rtrim($path, '/');
$items[$path] = $item;
}
}
if (!empty($items[$base_path . '/login'])) {
$items[$base_path . '/login']['access callback'] = 'user_is_anonymous';
}
$items['admin/config/services/restful'] = array(
'title' => 'RESTful',
'description' => 'Administer the RESTful module.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'restful_admin_settings',
),
'access arguments' => array(
'administer restful',
),
'file' => 'restful.admin.inc',
);
$items['admin/config/services/restful/restful'] = $items['admin/config/services/restful'];
$items['admin/config/services/restful/restful']['type'] = MENU_DEFAULT_LOCAL_TASK;
$items['admin/config/services/restful/cache'] = array(
'title' => 'Cache',
'description' => 'Administer the RESTful module cache system.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'restful_admin_cache_settings',
),
'access arguments' => array(
'administer restful',
),
'file' => 'restful.cache.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 2,
);
return $items;
}
function restful_permission() {
return array(
'administer restful' => array(
'title' => t('Administer the RESTful module'),
'description' => t('Access the administration pages for the RESTful module.'),
),
'administer restful resources' => array(
'title' => t('Administer the resources'),
'description' => t('Perform operations on the resources.'),
),
'restful clear render caches' => array(
'title' => t('Clear RESTful render caches'),
'description' => t('Clear the render caches and their correspoding cache fragments.'),
),
);
}
function restful_help($path, $arg) {
switch ($path) {
case 'admin/config/services/restful':
case 'admin/help#restful':
$message = t('This module is managed in GitHub. Please make sure to read the files in the !link folder for more help.', array(
'!link' => l(t('Docs'), 'https://github.com/RESTful-Drupal/restful/tree/7.x-2.x/docs'),
));
return '<p>' . $message . '</p>';
case 'admin/config/services/restful/cache':
$message = t('The RESTful module contains several layers of caching for enhanced performance: (1) page cache (aka URL level caching) for anonymous users. This cache is extremely fast, but not very flexible. (2) The render cache can be configured for each resource and allows you to serve cached versions of your records (even to authenticated users!). The render cache also contains smart invalidation, which means that you do not need to have a TTL based cache system. Instead the caches are evicted when automatically when necessary.');
return '<p>' . $message . '</p>';
}
}
function restful() {
static $manager;
if (!isset($manager)) {
$manager = RestfulManager::createFromGlobals();
}
return $manager;
}
function restful_menu_access_callback($resource_name, $version_string = NULL) {
$resource_manager = restful()
->getResourceManager();
if (!empty($version_string) && preg_match('/v[0-9]+(\\.[0-9]+)?/', $version_string)) {
$version_string = substr($version_string, 1);
$parsed_versions = explode('.', $version_string);
if (count($parsed_versions) == 2) {
$versions = $parsed_versions;
}
}
if (empty($versions) && !($versions = $resource_manager
->getVersionFromRequest())) {
return FALSE;
}
try {
$instance_id = $resource_name . PluginBase::DERIVATIVE_SEPARATOR . implode('.', $versions);
$resource = $resource_manager
->getPlugin($instance_id, restful()
->getRequest());
if (!$resource) {
throw new PluginNotFoundException($instance_id);
}
return $resource
->access();
} catch (RestfulException $e) {
$response = restful()
->getResponse();
$output = _restful_build_http_api_error($e, $response);
$response
->setStatusCode($e
->getCode());
$response
->setContent(drupal_json_encode($output));
$response
->send();
exit;
} catch (PluginNotFoundException $e) {
restful_delivery(MENU_NOT_FOUND);
exit;
}
}
function restful_menu_process_callback($resource_name, $version = NULL) {
$path = func_get_args();
array_shift($path);
if (preg_match('/^v\\d+(\\.\\d+)?$/', $version)) {
array_shift($path);
}
$resource_manager = restful()
->getResourceManager();
list($major_version, $minor_version) = $resource_manager
->getVersionFromRequest();
$request = restful()
->getRequest();
$request
->setViaRouter(TRUE);
$resource = $resource_manager
->getPlugin($resource_name . PluginBase::DERIVATIVE_SEPARATOR . $major_version . '.' . $minor_version, $request);
$response_headers = restful()
->getResponse()
->getHeaders();
$version_array = $resource
->getVersion();
$version_string = 'v' . $version_array['major'] . '.' . $version_array['minor'];
$response_headers
->add(HttpHeader::create('X-API-Version', $version_string));
$vary = $request
->getHeaders()
->get('Vary')
->getValueString() ?: '';
$additional_variations = array(
$vary,
'Accept',
);
if ($x_api_version = $request
->getHeaders()
->get('X-API-Version')
->getValueString()) {
$additional_variations[] = 'X-API-Version';
}
if ($additional_variations) {
$response_headers
->append(HttpHeader::create('Vary', implode(',', $additional_variations)));
}
$plugin_definition = $resource
->getPluginDefinition();
if (!empty($plugin_definition['allowOrigin'])) {
$response_headers
->append(HttpHeader::create('Access-Control-Allow-Origin', $plugin_definition['allowOrigin']));
}
try {
$resource
->setPath(implode('/', $path));
$result = $resource
->process();
} catch (RestfulException $e) {
$result = _restful_build_http_api_error($e);
} catch (Exception $e) {
$result = array(
'type' => 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1',
'title' => $e
->getMessage(),
'status' => 500,
);
}
$resource
->switchUserBack();
return $result;
}
function restful_delivery($var = NULL) {
if (!isset($var)) {
return;
}
$request = restful()
->getRequest();
$response = restful()
->getResponse();
if (!empty($var['status'])) {
$response
->setStatusCode($var['status']);
}
if (is_int($var)) {
_restful_get_data_from_menu_status($var);
if (!empty($var['status'])) {
$response
->setStatusCode($var['status']);
}
try {
$formatter_id = variable_get('restful_default_output_formatter', 'json');
$data = restful()
->getFormatterManager()
->negotiateFormatter(NULL, $formatter_id)
->format($var);
$response
->setContent($data);
$response
->prepare($request);
$response
->send();
} catch (RestfulException $e) {
$output = _restful_build_http_api_error($e, $response);
$response
->setStatusCode($e
->getCode());
$response
->setContent(drupal_json_encode($output));
$response
->send();
return;
}
return;
}
try {
$resource = restful()
->getResourceManager()
->negotiate();
$formatter_manager = restful()
->getFormatterManager();
$formatter_manager
->setResource($resource);
$plugin_definition = $resource
->getPluginDefinition();
if ($request
->getMethod() == RequestInterface::METHOD_OPTIONS) {
$formatter_name = 'json';
}
else {
$formatter_name = isset($plugin_definition['formatter']) ? $plugin_definition['formatter'] : NULL;
}
$output = $formatter_manager
->format($var, $formatter_name);
$response
->setContent($output);
} catch (RestfulException $e) {
$output = _restful_build_http_api_error($e, $response);
$response
->setStatusCode($e
->getCode());
$response
->setContent(drupal_json_encode($output));
$response
->send();
return;
}
$response
->prepare($request);
$response
->send();
}
function _restful_get_data_from_menu_status(&$var) {
switch ($var) {
case MENU_NOT_FOUND:
$class_name = '\\Drupal\\restful\\Exception\\NotFoundException';
$message = 'Invalid URL path.';
break;
case MENU_ACCESS_DENIED:
$class_name = '\\Drupal\\restful\\Exception\\ForbiddenException';
$message = 'Access denied.';
break;
case MENU_SITE_OFFLINE:
$class_name = '\\Drupal\\restful\\Exception\\ServiceUnavailableException';
$message = 'Site is offline.';
break;
default:
$class_name = '\\Drupal\\restful\\Exception\\RestfulException';
$message = 'Unknown exception';
}
$var = _restful_build_http_api_error(new $class_name($message));
}
function _restful_build_http_api_error(RestfulException $exception, ResponseInterface $response = NULL) {
$response = $response ?: restful()
->getResponse();
$exception
->setHeader('Content-Type', 'application/problem+json; charset=utf-8');
$result = array(
'type' => $exception
->getType(),
'title' => $exception
->getMessage(),
'status' => $exception
->getCode(),
'detail' => $exception
->getDescription(),
);
if ($instance = $exception
->getInstance()) {
$result['instance'] = $instance;
}
if ($errors = $exception
->getFieldErrors()) {
$result['errors'] = $errors;
}
$headers = $response
->getHeaders();
foreach ($exception
->getHeaders() as $header_name => $header_value) {
$headers
->add(HttpHeader::create($header_name, $header_value));
}
drupal_page_is_cacheable(FALSE);
if ($exception
->getCode() < 500) {
watchdog('restful', $exception
->getMessage());
}
else {
watchdog_exception('restful', $exception);
}
return $result;
}
function restful_page_delivery_callback_alter(&$callback) {
if (!variable_get('restful_hijack_api_pages', TRUE)) {
return;
}
$base_path = variable_get('restful_hook_menu_base_path', 'api');
if (strpos($_GET['q'], $base_path . '/') !== 0 && $_GET['q'] != $base_path) {
return;
}
if (menu_get_item()) {
return;
}
$callback = 'restful_deliver_menu_not_found';
}
function restful_deliver_menu_not_found($page_callback_result) {
restful_delivery(MENU_NOT_FOUND);
}
function restful_cron() {
\Drupal\restful\RateLimit\RateLimitManager::deleteExpired();
}
function restful_csrf_session_token() {
return array(
'X-CSRF-Token' => drupal_get_token(\Drupal\restful\Plugin\authentication\Authentication::TOKEN_VALUE),
);
}
function restful_date_time_format_element_validate($element, &$form_state) {
$value = $element['#value'];
try {
new \DateInterval($value);
} catch (\Exception $e) {
form_error($element, t('%name must be compatible with the !link.', array(
'%name' => $element['#title'],
'!link' => l(t('\\DateInterval format'), 'http://php.net/manual/en/class.dateinterval.php'),
)));
}
}
function restful_restful_resource_alter(ResourceInterface &$resource) {
$disabled_plugins = array(
'files_upload:1.0' => (bool) (!variable_get('restful_file_upload', FALSE)),
'users:1.0' => (bool) (!variable_get('restful_enable_users_resource', TRUE)),
'login_cookie:1.0' => (bool) (!variable_get('restful_enable_user_login_resource', TRUE)),
'discovery:1.0' => (bool) (!variable_get('restful_enable_discovery_resource', TRUE)),
) + variable_get('restful_disabled_plugins', array());
if (!empty($disabled_plugins[$resource
->getResourceName()])) {
$resource
->disable();
}
elseif (isset($disabled_plugins[$resource
->getResourceName()]) && $disabled_plugins[$resource
->getResourceName()] === FALSE && !$resource
->isEnabled()) {
$resource
->enable();
}
$plugin_definition = $resource
->getPluginDefinition();
if (!empty($plugin_definition['renderCache']['render']) || !isset($plugin_definition['renderCache']['render']) && variable_get('restful_render_cache', FALSE)) {
$resource = new CacheDecoratedResource($resource);
}
if (!empty($plugin_definition['rateLimit']) || variable_get('restful_global_rate_limit', 0)) {
$resource = new RateLimitDecoratedResource($resource);
}
if ($resource
->getResourceMachineName() == 'discovery' && !variable_get('restful_enable_discovery_resource', TRUE) && $resource
->isEnabled()) {
$resource
->disable();
}
}