danielebarchiesi@4
|
1 <?php
|
danielebarchiesi@4
|
2
|
danielebarchiesi@4
|
3 /**
|
danielebarchiesi@4
|
4 * @file
|
danielebarchiesi@4
|
5 * RESTful web services module.
|
danielebarchiesi@4
|
6 */
|
danielebarchiesi@4
|
7
|
danielebarchiesi@4
|
8 /**
|
danielebarchiesi@4
|
9 * Returns info about all defined resources.
|
danielebarchiesi@4
|
10 *
|
danielebarchiesi@4
|
11 * @param string $resource
|
danielebarchiesi@4
|
12 * By default null, else the info for the given resource will be returned.
|
danielebarchiesi@4
|
13 */
|
danielebarchiesi@4
|
14 function restws_get_resource_info($resource = NULL) {
|
danielebarchiesi@4
|
15 $info = &drupal_static(__FUNCTION__);
|
danielebarchiesi@4
|
16 if (!isset($info)) {
|
danielebarchiesi@4
|
17 $info = module_invoke_all('restws_resource_info');
|
danielebarchiesi@4
|
18 drupal_alter('restws_resource_info', $info);
|
danielebarchiesi@4
|
19 }
|
danielebarchiesi@4
|
20 if (!empty($resource)) {
|
danielebarchiesi@4
|
21 return $info[$resource];
|
danielebarchiesi@4
|
22 }
|
danielebarchiesi@4
|
23 return $info;
|
danielebarchiesi@4
|
24 }
|
danielebarchiesi@4
|
25
|
danielebarchiesi@4
|
26 /**
|
danielebarchiesi@4
|
27 * Returns info about all defined formats.
|
danielebarchiesi@4
|
28 */
|
danielebarchiesi@4
|
29 function restws_get_format_info() {
|
danielebarchiesi@4
|
30 $info = &drupal_static(__FUNCTION__);
|
danielebarchiesi@4
|
31 if (!isset($info)) {
|
danielebarchiesi@4
|
32 $info = module_invoke_all('restws_format_info');
|
danielebarchiesi@4
|
33 drupal_alter('restws_format_info', $info);
|
danielebarchiesi@4
|
34 }
|
danielebarchiesi@4
|
35 return $info;
|
danielebarchiesi@4
|
36 }
|
danielebarchiesi@4
|
37
|
danielebarchiesi@4
|
38 /**
|
danielebarchiesi@4
|
39 * Implements hook_restws_resource_info().
|
danielebarchiesi@4
|
40 *
|
danielebarchiesi@4
|
41 * Provides resources for all entity types.
|
danielebarchiesi@4
|
42 */
|
danielebarchiesi@4
|
43 function restws_restws_resource_info() {
|
danielebarchiesi@4
|
44 foreach (entity_get_info() as $entity_type => $info) {
|
danielebarchiesi@4
|
45 $result[$entity_type] = array(
|
danielebarchiesi@4
|
46 'label' => $info['label'],
|
danielebarchiesi@4
|
47 'class' => 'RestWSEntityResourceController',
|
danielebarchiesi@4
|
48 );
|
danielebarchiesi@4
|
49 }
|
danielebarchiesi@4
|
50 return $result;
|
danielebarchiesi@4
|
51 }
|
danielebarchiesi@4
|
52
|
danielebarchiesi@4
|
53 /**
|
danielebarchiesi@4
|
54 * Returns a instance of a resource controller.
|
danielebarchiesi@4
|
55 *
|
danielebarchiesi@4
|
56 * @return RestWSResourceControllerInterface
|
danielebarchiesi@4
|
57 * A resource controller object.
|
danielebarchiesi@4
|
58 */
|
danielebarchiesi@4
|
59 function restws_resource_controller($name) {
|
danielebarchiesi@4
|
60 $static = &drupal_static(__FUNCTION__);
|
danielebarchiesi@4
|
61 if (!isset($static[$name])) {
|
danielebarchiesi@4
|
62 $info = restws_get_resource_info();
|
danielebarchiesi@4
|
63 $static[$name] = isset($info[$name]) ? new $info[$name]['class']($name, $info[$name]) : FALSE;
|
danielebarchiesi@4
|
64 }
|
danielebarchiesi@4
|
65 return $static[$name];
|
danielebarchiesi@4
|
66 }
|
danielebarchiesi@4
|
67
|
danielebarchiesi@4
|
68 /**
|
danielebarchiesi@4
|
69 * Implements hook_restws_format_info().
|
danielebarchiesi@4
|
70 *
|
danielebarchiesi@4
|
71 * Provides basic formats.
|
danielebarchiesi@4
|
72 */
|
danielebarchiesi@4
|
73 function restws_restws_format_info() {
|
danielebarchiesi@4
|
74 $result = array(
|
danielebarchiesi@4
|
75 'json' => array(
|
danielebarchiesi@4
|
76 'label' => t('JSON'),
|
danielebarchiesi@4
|
77 'class' => 'RestWSFormatJSON',
|
danielebarchiesi@4
|
78 'mime type' => 'application/json',
|
danielebarchiesi@4
|
79 ),
|
danielebarchiesi@4
|
80 'xml' => array(
|
danielebarchiesi@4
|
81 'label' => t('XML'),
|
danielebarchiesi@4
|
82 'class' => 'RestWSFormatXML',
|
danielebarchiesi@4
|
83 'mime type' => 'application/xml',
|
danielebarchiesi@4
|
84 ),
|
danielebarchiesi@4
|
85 );
|
danielebarchiesi@4
|
86 if (module_exists('rdf')) {
|
danielebarchiesi@4
|
87 $result['rdf'] = array(
|
danielebarchiesi@4
|
88 'label' => t('RDF'),
|
danielebarchiesi@4
|
89 'class' => 'RestWSFormatRDF',
|
danielebarchiesi@4
|
90 'mime type' => 'application/rdf+xml',
|
danielebarchiesi@4
|
91 );
|
danielebarchiesi@4
|
92 }
|
danielebarchiesi@4
|
93 return $result;
|
danielebarchiesi@4
|
94 }
|
danielebarchiesi@4
|
95
|
danielebarchiesi@4
|
96 /**
|
danielebarchiesi@4
|
97 * Returns an instance of a format.
|
danielebarchiesi@4
|
98 *
|
danielebarchiesi@4
|
99 * @return RestWSFormatInterface
|
danielebarchiesi@4
|
100 * A resource format object.
|
danielebarchiesi@4
|
101 */
|
danielebarchiesi@4
|
102 function restws_format($name) {
|
danielebarchiesi@4
|
103 $static = &drupal_static(__FUNCTION__);
|
danielebarchiesi@4
|
104 if (!isset($static[$name])) {
|
danielebarchiesi@4
|
105 $info = restws_get_format_info();
|
danielebarchiesi@4
|
106 $static[$name] = isset($info[$name]) ? new $info[$name]['class']($name, $info[$name]) : FALSE;
|
danielebarchiesi@4
|
107 }
|
danielebarchiesi@4
|
108 return $static[$name];
|
danielebarchiesi@4
|
109 }
|
danielebarchiesi@4
|
110
|
danielebarchiesi@4
|
111 /**
|
danielebarchiesi@4
|
112 * Handles a request.
|
danielebarchiesi@4
|
113 *
|
danielebarchiesi@4
|
114 * @param string $op
|
danielebarchiesi@4
|
115 * One of 'create', 'update', 'delete' or 'view'.
|
danielebarchiesi@4
|
116 */
|
danielebarchiesi@4
|
117 function restws_handle_request($op, $format, $resource_name, $id = NULL, $payload = NULL) {
|
danielebarchiesi@4
|
118 if ($resource = restws_resource_controller($resource_name)) {
|
danielebarchiesi@4
|
119 // Allow other modules to change the web service request or react upon it.
|
danielebarchiesi@4
|
120 $request = array(
|
danielebarchiesi@4
|
121 'op' => &$op,
|
danielebarchiesi@4
|
122 'format' => &$format,
|
danielebarchiesi@4
|
123 'resource' => &$resource,
|
danielebarchiesi@4
|
124 'id' => &$id,
|
danielebarchiesi@4
|
125 'payload' => &$payload,
|
danielebarchiesi@4
|
126 );
|
danielebarchiesi@4
|
127 drupal_alter('restws_request', $request);
|
danielebarchiesi@4
|
128
|
danielebarchiesi@4
|
129 // Since there is no access callback for query we need to use view.
|
danielebarchiesi@4
|
130 $access_op = $op == 'query' ? 'view' : $op;
|
danielebarchiesi@4
|
131
|
danielebarchiesi@4
|
132 if (user_access('access resource ' . $resource_name) && $resource->access($access_op, $id)) {
|
danielebarchiesi@4
|
133 try {
|
danielebarchiesi@4
|
134 $method = $op . 'Resource';
|
danielebarchiesi@4
|
135 if ($op == 'create') {
|
danielebarchiesi@4
|
136 print $format->$method($resource, $payload);
|
danielebarchiesi@4
|
137 drupal_add_http_header('Status', '201 Created');
|
danielebarchiesi@4
|
138 }
|
danielebarchiesi@4
|
139 elseif ($op == 'query') {
|
danielebarchiesi@4
|
140 if (!$resource instanceof RestWSQueryResourceControllerInterface) {
|
danielebarchiesi@4
|
141 throw new RestWSException('Quering not available for this resources', 501);
|
danielebarchiesi@4
|
142 }
|
danielebarchiesi@4
|
143 print $format->$method($resource, $payload);
|
danielebarchiesi@4
|
144 }
|
danielebarchiesi@4
|
145 else {
|
danielebarchiesi@4
|
146 print $format->$method($resource, $id, $payload);
|
danielebarchiesi@4
|
147 }
|
danielebarchiesi@4
|
148 drupal_add_http_header('Content-Type', $format->mimeType());
|
danielebarchiesi@4
|
149 }
|
danielebarchiesi@4
|
150 catch (RestWSException $e) {
|
danielebarchiesi@4
|
151 echo check_plain($e->getHTTPError()) . ': ' . check_plain($e->getMessage());
|
danielebarchiesi@4
|
152 drupal_add_http_header('Status', $e->getHTTPError());
|
danielebarchiesi@4
|
153 }
|
danielebarchiesi@4
|
154 }
|
danielebarchiesi@4
|
155 else {
|
danielebarchiesi@4
|
156 echo '403 Forbidden';
|
danielebarchiesi@4
|
157 drupal_add_http_header('Status', '403 Forbidden');
|
danielebarchiesi@4
|
158 watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
|
danielebarchiesi@4
|
159 }
|
danielebarchiesi@4
|
160 }
|
danielebarchiesi@4
|
161 else {
|
danielebarchiesi@4
|
162 echo '404 Not Found';
|
danielebarchiesi@4
|
163 drupal_add_http_header('Status', '404 Not Found');
|
danielebarchiesi@4
|
164 }
|
danielebarchiesi@4
|
165 drupal_page_footer();
|
danielebarchiesi@4
|
166 exit;
|
danielebarchiesi@4
|
167 }
|
danielebarchiesi@4
|
168
|
danielebarchiesi@4
|
169 /**
|
danielebarchiesi@4
|
170 * An exception defining the HTTP error code and message.
|
danielebarchiesi@4
|
171 */
|
danielebarchiesi@4
|
172 class RestWSException extends Exception {
|
danielebarchiesi@4
|
173
|
danielebarchiesi@4
|
174 public function getHTTPError() {
|
danielebarchiesi@4
|
175 $code = $this->getCode();
|
danielebarchiesi@4
|
176 switch ($code) {
|
danielebarchiesi@4
|
177 case 403:
|
danielebarchiesi@4
|
178 return '403 Forbidden';
|
danielebarchiesi@4
|
179 case 404:
|
danielebarchiesi@4
|
180 return '404 Not Found';
|
danielebarchiesi@4
|
181 case 406:
|
danielebarchiesi@4
|
182 return '406 Not Acceptable';
|
danielebarchiesi@4
|
183 case 412:
|
danielebarchiesi@4
|
184 return '412 Precondition Failed';
|
danielebarchiesi@4
|
185 case 422:
|
danielebarchiesi@4
|
186 return '422 Unprocessable Entity';
|
danielebarchiesi@4
|
187 default:
|
danielebarchiesi@4
|
188 return '500 Internal Server Error';
|
danielebarchiesi@4
|
189 }
|
danielebarchiesi@4
|
190 }
|
danielebarchiesi@4
|
191 }
|
danielebarchiesi@4
|
192
|
danielebarchiesi@4
|
193 /**
|
danielebarchiesi@4
|
194 * Implements hook_menu_alter().
|
danielebarchiesi@4
|
195 */
|
danielebarchiesi@4
|
196 function restws_menu_alter(&$items) {
|
danielebarchiesi@4
|
197 foreach (restws_get_resource_info() as $resource => $info) {
|
danielebarchiesi@4
|
198 // Resource full path (e.g. /node/% or /user/%) for accessing specific
|
danielebarchiesi@4
|
199 // resources.
|
danielebarchiesi@4
|
200 $menu_path = isset($info['menu_path']) ? $info['menu_path'] . '/%' : $resource . '/%';
|
danielebarchiesi@4
|
201 // Replace existing page callbacks with our own (e.g. node/%)
|
danielebarchiesi@4
|
202 if (isset($items[$menu_path])) {
|
danielebarchiesi@4
|
203 // Prepend the page callback and the resource to the page arguments.
|
danielebarchiesi@4
|
204 // So we can re-use it on standard HTML page requests.
|
danielebarchiesi@4
|
205 array_unshift($items[$menu_path]['page arguments'], $resource, $items[$menu_path]['page callback']);
|
danielebarchiesi@4
|
206 $items[$menu_path]['page callback'] = 'restws_page_callback';
|
danielebarchiesi@4
|
207 }
|
danielebarchiesi@4
|
208 // Also replace wildcard loaders (e.g. node/%node)
|
danielebarchiesi@4
|
209 elseif (isset($items[$menu_path . $resource])) {
|
danielebarchiesi@4
|
210 $menu_path = $menu_path . $resource;
|
danielebarchiesi@4
|
211 array_unshift($items[$menu_path]['page arguments'], $resource, $items[$menu_path]['page callback']);
|
danielebarchiesi@4
|
212 $items[$menu_path]['page callback'] = 'restws_page_callback';
|
danielebarchiesi@4
|
213 }
|
danielebarchiesi@4
|
214 else {
|
danielebarchiesi@4
|
215 $items[$menu_path] = array(
|
danielebarchiesi@4
|
216 'page callback' => 'restws_page_callback',
|
danielebarchiesi@4
|
217 'page arguments' => array($resource),
|
danielebarchiesi@4
|
218 'access callback' => TRUE,
|
danielebarchiesi@4
|
219 'type' => MENU_CALLBACK,
|
danielebarchiesi@4
|
220 );
|
danielebarchiesi@4
|
221 }
|
danielebarchiesi@4
|
222 // Resource base path (e.g. /node or /user) for creating resources.
|
danielebarchiesi@4
|
223 $menu_path = isset($info['menu_path']) ? substr($menu_path, 0, strlen($menu_path) - 2) : $resource;
|
danielebarchiesi@4
|
224
|
danielebarchiesi@4
|
225 if (isset($items[$menu_path])) {
|
danielebarchiesi@4
|
226 // Prepend the page callback and the resource to the page arguments.
|
danielebarchiesi@4
|
227 if (!isset($items[$menu_path]['page arguments'])) {
|
danielebarchiesi@4
|
228 $items[$menu_path]['page arguments'] = array();
|
danielebarchiesi@4
|
229 }
|
danielebarchiesi@4
|
230 array_unshift($items[$menu_path]['page arguments'], $resource, $items[$menu_path]['page callback']);
|
danielebarchiesi@4
|
231 $items[$menu_path]['page callback'] = 'restws_page_callback';
|
danielebarchiesi@4
|
232 }
|
danielebarchiesi@4
|
233 else {
|
danielebarchiesi@4
|
234 $items[$menu_path] = array(
|
danielebarchiesi@4
|
235 'page callback' => 'restws_page_callback',
|
danielebarchiesi@4
|
236 'page arguments' => array($resource),
|
danielebarchiesi@4
|
237 'access callback' => TRUE,
|
danielebarchiesi@4
|
238 'type' => MENU_CALLBACK,
|
danielebarchiesi@4
|
239 );
|
danielebarchiesi@4
|
240 }
|
danielebarchiesi@4
|
241 // Querying menu paths.
|
danielebarchiesi@4
|
242 foreach (array_keys(restws_get_format_info()) as $format) {
|
danielebarchiesi@4
|
243 // Resource base path URLs with the suffixes (e.g. node.json or user.xml)
|
danielebarchiesi@4
|
244 // for querying.
|
danielebarchiesi@4
|
245 if (isset($items["$menu_path.$format"])) {
|
danielebarchiesi@4
|
246 // Prepend the page callback and the resource to the page arguments.
|
danielebarchiesi@4
|
247 if (!isset($items["$menu_path.$format"]['page arguments'])) {
|
danielebarchiesi@4
|
248 $items["$menu_path.$format"]['page arguments'] = array();
|
danielebarchiesi@4
|
249 }
|
danielebarchiesi@4
|
250 array_unshift($items["$menu_path.$format"]['page arguments'], $resource, $items["$menu_path.$format"]['page callback']);
|
danielebarchiesi@4
|
251 $items["$menu_path.$format"]['page callback'] = 'restws_page_callback';
|
danielebarchiesi@4
|
252
|
danielebarchiesi@4
|
253 }
|
danielebarchiesi@4
|
254 else {
|
danielebarchiesi@4
|
255 $items["$menu_path.$format"] = array(
|
danielebarchiesi@4
|
256 'page callback' => 'restws_page_callback',
|
danielebarchiesi@4
|
257 'page arguments' => array($resource),
|
danielebarchiesi@4
|
258 'access callback' => TRUE,
|
danielebarchiesi@4
|
259 'type' => MENU_CALLBACK,
|
danielebarchiesi@4
|
260 );
|
danielebarchiesi@4
|
261 }
|
danielebarchiesi@4
|
262 }
|
danielebarchiesi@4
|
263 }
|
danielebarchiesi@4
|
264 }
|
danielebarchiesi@4
|
265
|
danielebarchiesi@4
|
266 /**
|
danielebarchiesi@4
|
267 * Menu page callback.
|
danielebarchiesi@4
|
268 */
|
danielebarchiesi@4
|
269 function restws_page_callback($resource, $page_callback = NULL) {
|
danielebarchiesi@4
|
270 $id_arg = arg(1);
|
danielebarchiesi@4
|
271 $resource_arg = arg(0);
|
danielebarchiesi@4
|
272 $format = FALSE;
|
danielebarchiesi@4
|
273 $id = NULL;
|
danielebarchiesi@4
|
274 // Check for an appended .format string on GET requests only to avoid CSRF
|
danielebarchiesi@4
|
275 // attacks on POST requests.
|
danielebarchiesi@4
|
276 if ($_SERVER['REQUEST_METHOD'] == 'GET' && ($pos = strpos($id_arg, '.')) && $format_name = substr($id_arg, $pos + 1)) {
|
danielebarchiesi@4
|
277 $id = substr($id_arg, 0, $pos);
|
danielebarchiesi@4
|
278 $format = restws_format($format_name);
|
danielebarchiesi@4
|
279 }
|
danielebarchiesi@4
|
280 elseif ($_SERVER['REQUEST_METHOD'] == 'GET' && ($pos = strpos($resource_arg, '.')) && $format_name = substr($resource_arg, $pos + 1)) {
|
danielebarchiesi@4
|
281 $format = restws_format($format_name);
|
danielebarchiesi@4
|
282 }
|
danielebarchiesi@4
|
283 else {
|
danielebarchiesi@4
|
284 $id = $id_arg;
|
danielebarchiesi@4
|
285 switch ($_SERVER['REQUEST_METHOD']) {
|
danielebarchiesi@4
|
286 case 'POST':
|
danielebarchiesi@4
|
287 case 'PUT':
|
danielebarchiesi@4
|
288 // Get format MIME type form HTTP Content type header.
|
danielebarchiesi@4
|
289 $parts = explode(';', $_SERVER['CONTENT_TYPE'], 2);
|
danielebarchiesi@4
|
290 $format = restws_format_mimetype($parts[0]);
|
danielebarchiesi@4
|
291 break;
|
danielebarchiesi@4
|
292
|
danielebarchiesi@4
|
293 case 'DELETE':
|
danielebarchiesi@4
|
294 if (isset($_SERVER['HTTP_ACCEPT'])) {
|
danielebarchiesi@4
|
295 $parts = explode(',', $_SERVER['HTTP_ACCEPT'], 2);
|
danielebarchiesi@4
|
296 $format = restws_format_mimetype($parts[0]);
|
danielebarchiesi@4
|
297 }
|
danielebarchiesi@4
|
298 if (!$format) {
|
danielebarchiesi@4
|
299 // We don't care about the format, just pick JSON.
|
danielebarchiesi@4
|
300 $format = restws_format('json');
|
danielebarchiesi@4
|
301 }
|
danielebarchiesi@4
|
302 break;
|
danielebarchiesi@4
|
303
|
danielebarchiesi@4
|
304 default:
|
danielebarchiesi@4
|
305 // Get the format MIME type form the HTTP Accept header.
|
danielebarchiesi@4
|
306 // Ignore requests from web browsers that accept HTML.
|
danielebarchiesi@4
|
307 if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === FALSE) {
|
danielebarchiesi@4
|
308 // Use the first MIME type.
|
danielebarchiesi@4
|
309 $parts = explode(',', $_SERVER['HTTP_ACCEPT'], 2);
|
danielebarchiesi@4
|
310 $format = restws_format_mimetype($parts[0]);
|
danielebarchiesi@4
|
311 }
|
danielebarchiesi@4
|
312 // Consumers should not use this URL if page caching is enabled.
|
danielebarchiesi@4
|
313 // Drupal's page cache IDs are only determined by URL path, so this
|
danielebarchiesi@4
|
314 // could poison the HTML page cache. A browser request to /node/1 could
|
danielebarchiesi@4
|
315 // suddenly return JSON if the cache was primed with this RESTWS
|
danielebarchiesi@4
|
316 // response.
|
danielebarchiesi@4
|
317 if ($format && !isset($_COOKIE[session_name()]) && variable_get('cache')) {
|
danielebarchiesi@4
|
318 // Redirect to the URL path containing the format name instead.
|
danielebarchiesi@4
|
319 drupal_goto($_GET['q'] . '.' . $format->getName(), array(), 301);
|
danielebarchiesi@4
|
320 }
|
danielebarchiesi@4
|
321 }
|
danielebarchiesi@4
|
322 }
|
danielebarchiesi@4
|
323 if ($format) {
|
danielebarchiesi@4
|
324 switch ($_SERVER['REQUEST_METHOD']) {
|
danielebarchiesi@4
|
325 case 'POST':
|
danielebarchiesi@4
|
326 $op = 'create';
|
danielebarchiesi@4
|
327 break;
|
danielebarchiesi@4
|
328
|
danielebarchiesi@4
|
329 case 'PUT':
|
danielebarchiesi@4
|
330 $op = 'update';
|
danielebarchiesi@4
|
331 break;
|
danielebarchiesi@4
|
332
|
danielebarchiesi@4
|
333 case 'DELETE':
|
danielebarchiesi@4
|
334 $op = 'delete';
|
danielebarchiesi@4
|
335 break;
|
danielebarchiesi@4
|
336
|
danielebarchiesi@4
|
337 default:
|
danielebarchiesi@4
|
338 if (!empty($id)) {
|
danielebarchiesi@4
|
339 $op = 'view';
|
danielebarchiesi@4
|
340 }
|
danielebarchiesi@4
|
341 else {
|
danielebarchiesi@4
|
342 $op = 'query';
|
danielebarchiesi@4
|
343 }
|
danielebarchiesi@4
|
344 }
|
danielebarchiesi@4
|
345
|
danielebarchiesi@4
|
346 // CSRF protection on write operations.
|
danielebarchiesi@4
|
347 if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD', 'OPTIONS', 'TRACE')) && !restws_csrf_validation()) {
|
danielebarchiesi@4
|
348 echo '403 Access Denied: CSRF validation failed';
|
danielebarchiesi@4
|
349 drupal_add_http_header('Status', '403 Forbidden');
|
danielebarchiesi@4
|
350 drupal_page_footer();
|
danielebarchiesi@4
|
351 exit;
|
danielebarchiesi@4
|
352 }
|
danielebarchiesi@4
|
353
|
danielebarchiesi@4
|
354 $payload = file_get_contents('php://input');
|
danielebarchiesi@4
|
355 if ($file = variable_get('restws_debug_log')) {
|
danielebarchiesi@4
|
356 $log = date(DATE_ISO8601) . "\n";
|
danielebarchiesi@4
|
357 $log .= 'Resource: ' . $resource . "\n";
|
danielebarchiesi@4
|
358 $log .= 'Operation: ' . $op . "\n";
|
danielebarchiesi@4
|
359 $log .= 'Format: ' . $format->mimeType() . "\n";
|
danielebarchiesi@4
|
360 $log .= 'Id: ' . $id . "\n";
|
danielebarchiesi@4
|
361 $log .= 'Payload: ' . $payload . "\n";
|
danielebarchiesi@4
|
362 $log .= "----------------------------------------------------------------\n";
|
danielebarchiesi@4
|
363 file_put_contents($file, $log, FILE_APPEND);
|
danielebarchiesi@4
|
364 }
|
danielebarchiesi@4
|
365 restws_handle_request($op, $format, $resource, $id, $payload);
|
danielebarchiesi@4
|
366 }
|
danielebarchiesi@4
|
367
|
danielebarchiesi@4
|
368 // @todo: Determine human readable URIs and redirect, if there is no
|
danielebarchiesi@4
|
369 // page callback.
|
danielebarchiesi@4
|
370 if (isset($page_callback)) {
|
danielebarchiesi@4
|
371 // Further page callback arguments have been appended to our arguments.
|
danielebarchiesi@4
|
372 $args = func_get_args();
|
danielebarchiesi@4
|
373 return call_user_func_array($page_callback, array_slice($args, 2));
|
danielebarchiesi@4
|
374 }
|
danielebarchiesi@4
|
375 echo '404 Not Found';
|
danielebarchiesi@4
|
376 drupal_add_http_header('Status', '404 Not Found');
|
danielebarchiesi@4
|
377 drupal_page_footer();
|
danielebarchiesi@4
|
378 exit;
|
danielebarchiesi@4
|
379 }
|
danielebarchiesi@4
|
380
|
danielebarchiesi@4
|
381 /**
|
danielebarchiesi@4
|
382 * Returns the URI used for the given resource.
|
danielebarchiesi@4
|
383 *
|
danielebarchiesi@4
|
384 * @param string $resource
|
danielebarchiesi@4
|
385 * The resource for which the URI should be returned.
|
danielebarchiesi@4
|
386 * @param int $id
|
danielebarchiesi@4
|
387 * The resource ID or NULL if only the base path should be returned.
|
danielebarchiesi@4
|
388 * @param array $options
|
danielebarchiesi@4
|
389 * Optional array that is passed to url().
|
danielebarchiesi@4
|
390 */
|
danielebarchiesi@4
|
391 function restws_resource_uri($resource, $id = NULL, array $options = array()) {
|
danielebarchiesi@4
|
392 $info = restws_get_resource_info($resource);
|
danielebarchiesi@4
|
393 $basepath = isset($info['menu_path']) ? $info['menu_path'] : $resource;
|
danielebarchiesi@4
|
394 $sub_path = isset($id) ? "/$id" : '';
|
danielebarchiesi@4
|
395
|
danielebarchiesi@4
|
396 // Avoid having the URLs aliased.
|
danielebarchiesi@4
|
397 $base_options = array('absolute' => TRUE, 'alias' => TRUE);
|
danielebarchiesi@4
|
398 $options += $base_options;
|
danielebarchiesi@4
|
399
|
danielebarchiesi@4
|
400 return url($basepath . $sub_path, $options);
|
danielebarchiesi@4
|
401 }
|
danielebarchiesi@4
|
402
|
danielebarchiesi@4
|
403 /**
|
danielebarchiesi@4
|
404 * Returns the format instance for a given MIME type.
|
danielebarchiesi@4
|
405 *
|
danielebarchiesi@4
|
406 * @param string $mime
|
danielebarchiesi@4
|
407 * The MIME type, e.g. 'application/json' or 'application/xml'.
|
danielebarchiesi@4
|
408 *
|
danielebarchiesi@4
|
409 * @return bool|RestWSFormatInterface
|
danielebarchiesi@4
|
410 * The format controller or FALSE if the format was not found.
|
danielebarchiesi@4
|
411 */
|
danielebarchiesi@4
|
412 function restws_format_mimetype($mime) {
|
danielebarchiesi@4
|
413 foreach (restws_get_format_info() as $format_name => $info) {
|
danielebarchiesi@4
|
414 if ($info['mime type'] == $mime) {
|
danielebarchiesi@4
|
415 return restws_format($format_name);
|
danielebarchiesi@4
|
416 }
|
danielebarchiesi@4
|
417 }
|
danielebarchiesi@4
|
418 return FALSE;
|
danielebarchiesi@4
|
419 }
|
danielebarchiesi@4
|
420
|
danielebarchiesi@4
|
421 /**
|
danielebarchiesi@4
|
422 * Implements hook_permission().
|
danielebarchiesi@4
|
423 */
|
danielebarchiesi@4
|
424 function restws_permission() {
|
danielebarchiesi@4
|
425 $permissions = array();
|
danielebarchiesi@4
|
426 // Create service access permissions per resource type.
|
danielebarchiesi@4
|
427 foreach (restws_get_resource_info() as $type => $info) {
|
danielebarchiesi@4
|
428 $permissions['access resource ' . $type] = array(
|
danielebarchiesi@4
|
429 'title' => t('Access the resource %resource', array('%resource' => $type)),
|
danielebarchiesi@4
|
430 );
|
danielebarchiesi@4
|
431 }
|
danielebarchiesi@4
|
432 return $permissions;
|
danielebarchiesi@4
|
433 }
|
danielebarchiesi@4
|
434
|
danielebarchiesi@4
|
435 /**
|
danielebarchiesi@4
|
436 * Implements hook_module_implements_alter().
|
danielebarchiesi@4
|
437 */
|
danielebarchiesi@4
|
438 function restws_module_implements_alter(&$implementations, $hook) {
|
danielebarchiesi@4
|
439 // Make sure that restws runs last.
|
danielebarchiesi@4
|
440 // @todo remove entity_info_alter once https://drupal.org/node/1780646 is fixed.
|
danielebarchiesi@4
|
441 if ($hook == 'menu_alter' || $hook == 'entity_info_alter') {
|
danielebarchiesi@4
|
442 $group = $implementations['restws'];
|
danielebarchiesi@4
|
443 unset($implementations['restws']);
|
danielebarchiesi@4
|
444 $implementations['restws'] = $group;
|
danielebarchiesi@4
|
445 }
|
danielebarchiesi@4
|
446 }
|
danielebarchiesi@4
|
447
|
danielebarchiesi@4
|
448 /**
|
danielebarchiesi@4
|
449 * Return all available meta controls.
|
danielebarchiesi@4
|
450 */
|
danielebarchiesi@4
|
451 function restws_meta_controls() {
|
danielebarchiesi@4
|
452 return array(
|
danielebarchiesi@4
|
453 'sort' => 'sort',
|
danielebarchiesi@4
|
454 'direction' => 'direction',
|
danielebarchiesi@4
|
455 'page' => 'page',
|
danielebarchiesi@4
|
456 'limit' => 'limit',
|
danielebarchiesi@4
|
457 'full' => 'full',
|
danielebarchiesi@4
|
458 );
|
danielebarchiesi@4
|
459 }
|
danielebarchiesi@4
|
460
|
danielebarchiesi@4
|
461 /**
|
danielebarchiesi@4
|
462 * Ensures that a request with cookies has the required CSRF header set.
|
danielebarchiesi@4
|
463 *
|
danielebarchiesi@4
|
464 * @return bool
|
danielebarchiesi@4
|
465 * TRUE if the request passed the CSRF protection, FALSE otherwise.
|
danielebarchiesi@4
|
466 */
|
danielebarchiesi@4
|
467 function restws_csrf_validation() {
|
danielebarchiesi@4
|
468 // This check only applies if the user was successfully authenticated and the
|
danielebarchiesi@4
|
469 // request comes with a session cookie.
|
danielebarchiesi@4
|
470 if (user_is_logged_in() && !empty($_COOKIE[session_name()])) {
|
danielebarchiesi@4
|
471 return isset($_SERVER['HTTP_X_CSRF_TOKEN']) && drupal_valid_token($_SERVER['HTTP_X_CSRF_TOKEN'], 'restws');
|
danielebarchiesi@4
|
472 }
|
danielebarchiesi@4
|
473 return TRUE;
|
danielebarchiesi@4
|
474 }
|
danielebarchiesi@4
|
475
|
danielebarchiesi@4
|
476 /**
|
danielebarchiesi@4
|
477 * Implements hook_menu().
|
danielebarchiesi@4
|
478 */
|
danielebarchiesi@4
|
479 function restws_menu() {
|
danielebarchiesi@4
|
480 $items['restws/session/token'] = array(
|
danielebarchiesi@4
|
481 'page callback' => 'restws_session_token',
|
danielebarchiesi@4
|
482 // Only authenticated users are allowed to retrieve a session token.
|
danielebarchiesi@4
|
483 'access callback' => 'user_is_logged_in',
|
danielebarchiesi@4
|
484 'type' => MENU_CALLBACK,
|
danielebarchiesi@4
|
485 );
|
danielebarchiesi@4
|
486 return $items;
|
danielebarchiesi@4
|
487 }
|
danielebarchiesi@4
|
488
|
danielebarchiesi@4
|
489 /**
|
danielebarchiesi@4
|
490 * Page callback: returns a session token for the currently active user.
|
danielebarchiesi@4
|
491 */
|
danielebarchiesi@4
|
492 function restws_session_token() {
|
danielebarchiesi@4
|
493 drupal_add_http_header('Content-Type', 'text/plain');
|
danielebarchiesi@4
|
494 print drupal_get_token('restws');
|
danielebarchiesi@4
|
495 drupal_exit();
|
danielebarchiesi@4
|
496 }
|
danielebarchiesi@4
|
497
|
danielebarchiesi@4
|
498 /**
|
danielebarchiesi@4
|
499 * Access callback for the node entity.
|
danielebarchiesi@4
|
500 *
|
danielebarchiesi@4
|
501 * Replacement for entity_metadata_no_hook_node_access() because it does not
|
danielebarchiesi@4
|
502 * work with the create operation.
|
danielebarchiesi@4
|
503 *
|
danielebarchiesi@4
|
504 * @todo Remove this once https://drupal.org/node/1780646 is fixed.
|
danielebarchiesi@4
|
505 *
|
danielebarchiesi@4
|
506 * @see restws_entity_info_alter()
|
danielebarchiesi@4
|
507 * @see entity_metadata_no_hook_node_access()
|
danielebarchiesi@4
|
508 */
|
danielebarchiesi@4
|
509 function restws_entity_node_access($op, $node = NULL, $account = NULL) {
|
danielebarchiesi@4
|
510 // First deal with the case where a $node is provided.
|
danielebarchiesi@4
|
511 if (isset($node)) {
|
danielebarchiesi@4
|
512 // Ugly hack to handle field access, because entity_api does not distinguish
|
danielebarchiesi@4
|
513 // between 'create' and 'update' permissions for fields. This should rather
|
danielebarchiesi@4
|
514 // be fixed in EntityStructureWrapper::propertyAccess() (entity.wrapper.inc).
|
danielebarchiesi@4
|
515 if ($op == 'update' && empty($node->nid)) {
|
danielebarchiesi@4
|
516 $op = 'create';
|
danielebarchiesi@4
|
517 }
|
danielebarchiesi@4
|
518 if ($op == 'create') {
|
danielebarchiesi@4
|
519 if (isset($node->type)) {
|
danielebarchiesi@4
|
520 return node_access($op, $node->type, $account);
|
danielebarchiesi@4
|
521 }
|
danielebarchiesi@4
|
522 else {
|
danielebarchiesi@4
|
523 throw new EntityMalformedException('Permission to create a node was requested but no node type was given.');
|
danielebarchiesi@4
|
524 }
|
danielebarchiesi@4
|
525 }
|
danielebarchiesi@4
|
526 // If a non-default revision is given, incorporate revision access.
|
danielebarchiesi@4
|
527 $default_revision = node_load($node->nid);
|
danielebarchiesi@4
|
528 if ($node->vid !== $default_revision->vid) {
|
danielebarchiesi@4
|
529 return _node_revision_access($node, $op, $account);
|
danielebarchiesi@4
|
530 }
|
danielebarchiesi@4
|
531 else {
|
danielebarchiesi@4
|
532 return node_access($op, $node, $account);
|
danielebarchiesi@4
|
533 }
|
danielebarchiesi@4
|
534 }
|
danielebarchiesi@4
|
535 // No node is provided. Check for access to all nodes.
|
danielebarchiesi@4
|
536 if (user_access('bypass node access', $account)) {
|
danielebarchiesi@4
|
537 return TRUE;
|
danielebarchiesi@4
|
538 }
|
danielebarchiesi@4
|
539 if (!user_access('access content', $account)) {
|
danielebarchiesi@4
|
540 return FALSE;
|
danielebarchiesi@4
|
541 }
|
danielebarchiesi@4
|
542 if ($op == 'view' && node_access_view_all_nodes($account)) {
|
danielebarchiesi@4
|
543 return TRUE;
|
danielebarchiesi@4
|
544 }
|
danielebarchiesi@4
|
545 return FALSE;
|
danielebarchiesi@4
|
546 }
|
danielebarchiesi@4
|
547
|
danielebarchiesi@4
|
548 /**
|
danielebarchiesi@4
|
549 * Implements hook_entity_info_alter().
|
danielebarchiesi@4
|
550 *
|
danielebarchiesi@4
|
551 * @todo Remove this once https://drupal.org/node/1780646 is fixed.
|
danielebarchiesi@4
|
552 */
|
danielebarchiesi@4
|
553 function restws_entity_info_alter(&$info) {
|
danielebarchiesi@4
|
554 // In order for this to work we have to make sure we run after entity_api.
|
danielebarchiesi@4
|
555 // @see restws_module_implements_alter().
|
danielebarchiesi@4
|
556 $info['node']['access callback'] = 'restws_entity_node_access';
|
danielebarchiesi@4
|
557 }
|