/[drupal]/contributions/modules/restapi/restapi.module
ViewVC logotype

Contents of /contributions/modules/restapi/restapi.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.6 - (show annotations) (download) (as text)
Thu Mar 20 22:12:43 2008 UTC (20 months, 1 week ago) by hanenkamp
Branch: MAIN
CVS Tags: HEAD
Changes since 1.5: +163 -16 lines
File MIME type: text/x-php
* Switched to json_encode() instead of drupal_to_js().
* Added support for a built-in JSON unserializer using json_decode().
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * This module provides a no fuss REST API to Drupal. It requires no special
7 * hooks to be implemented by your modules, it just takes advantage of the
8 * existing Forms API of Drupal to go.
9 *
10 * It doesn't perform anything special for user restrictions or logins. However,
11 * it doesn't bypass such things either. Any agent using the API will somehow
12 * need to authenticate with your Drupal installation in whatever way your site
13 * requires. I'm open to suggestions if you have an idea how this can be made
14 * better.
15 *
16 * This modeled heavily after the very nice REST API built for the Jifty
17 * Application Framework.
18 */
19
20 /**
21 * Implements hook_init(). Sets up the REST_API_ACCEPT and REQUEST_METHOD values
22 * for use with the API.
23 */
24 function restapi_init() {
25 $rest_args = explode('/', $_GET['q']);
26
27 // Do not do anything unless the first argument is '='
28 if ($rest_args[0] == '=') {
29
30 $last_index = count($rest_args) - 1;
31
32 // Munge REQUEST_METHOD if this is a POST and they appeneded !METHOD
33 $matches = array();
34 if ($_SERVER['REQUEST_METHOD'] == 'POST' && preg_match('/^(.*)!(DELETE|PUT|GET|POST|OPTIONS|HEAD|TRACE|CONNECT)$/', $rest_args[$last_index], $matches)) {
35
36 $rest_args[$last_index] = $matches[1];
37 $_SERVER['REQUEST_METHOD'] = $matches[2];
38 }
39
40 // Munge HTTP_ACCEPT if they've given us a file suffix
41 global $REST_API_ACCEPT;
42 $matches = array();
43 if (preg_match('/^(.*)\.(js|json|php)$/', $rest_args[$last_index], $matches)) {
44
45 $rest_args[$last_index] = $matches[1];
46 $REST_API_ACCEPT = $matches[2];
47 }
48
49 else {
50 $REST_API_ACCEPT = $_SERVER['HTTP_ACCEPT'];
51 }
52
53 $_GET['q'] = implode('/', $rest_args);
54 }
55 }
56
57 /**
58 * Implements hook_help(). Gives a help screen directing the user to /=/help.
59 *
60 * @param $section the section of help being shown
61 */
62 function restapi_help($section) {
63 switch ($section) {
64 case 'admin/help#restapi':
65 return '<p>'.t('The REST API provides a central location for help. To see what is available, go to the !help page.', array( '!help' => l(t('REST API Help'), '=/help') )).'</p>';
66 }
67 }
68
69 /**
70 * Implements hook_perm(). Returns the permissions used by the REST API module.
71 *
72 * The "access rest api" permission grants access to /=/*
73 */
74 function restapi_perm() {
75 return array( 'access rest api' );
76 }
77
78 /**
79 * Implements hook_restapi_menu().
80 *
81 * The hook_restapi_menu() hook works very similar to the regular hook_menu().
82 * The hook will be passed the same $may_cache variable as hook_menu(). It will
83 * be expected to return an array of arrays formatted in nearly the same way.
84 * The individual items returned should consider the following differences from
85 * the items returned from hook_menu():
86 *
87 * <ul>
88 * <li>"path": In general, this should always start with "=/". It's possible to
89 * use a different path, but not preferred.</li>
90 * <li>"method": This is an additional argument that may be passed. The
91 * following values are available:
92 * <ul>
93 * <li>"GET": If the given callback handles GET requests.</li>
94 * <li>"POST": If the given callback handles POST requests.</li>
95 * <li>"PUT": If the given callback handles PUT requests.</li>
96 * <li>"DELETE": If the given callback handles DELETE requests.</li>
97 * </ul>
98 * If no "method" is given, then "GET" is assumed.</li>
99 * <li>"access": If not given, it will default to the value of
100 * user_access("access rest api")</li>
101 * <li>"title": This parameter is ignored.</li>
102 * <li>"type": This parameter is ignored.</li>
103 * </ul>
104 *
105 * The "callback" and "callback arguments" parameters continue to work as they
106 * do in hook_menu().
107 *
108 * This will be much nicer after the Drupal 6 upgrade.
109 */
110 function restapi_restapi_menu($may_cache) {
111 if ($may_cache) {
112 $items[] = array(
113 'path' => '=/help',
114 'method' => 'GET',
115 'callback' => 'restapi_do_help',
116 );
117 }
118
119 return $items;
120 }
121
122 /**
123 * Implements hook_menu().
124 *
125 * This sets up the menu using all the available hook_restapi_menu() hooks.
126 */
127 function restapi_menu($may_cache) {
128 $items_by_path = array();
129
130 foreach (module_implements('restapi_menu') as $module) {
131 $restapi_items = module_invoke($module, 'restapi_menu', $may_cache);
132
133 if (!empty($restapi_items)) {
134 foreach ($restapi_items as $restapi_item) {
135 if (!$restapi_item['method']) {
136 $restapi_item['method'] = 'GET';
137 }
138
139 $access = empty($restapi_item['access']) ? user_access('access rest api')
140 : $restapi_item['access'];
141
142 if ($items_by_path[ $restapi_item['path'] ]) {
143 $items_by_path[ $restapi_item['path'] ]['callback arguments'][0][$restapi_item['method']] = $restapi_item;
144 }
145
146 else {
147
148 $items_by_path[ $restapi_item['path'] ] = array(
149 'path' => $restapi_item['path'],
150 'title' => t('REST API @path', array('@path' => $restapi_item['path'])),
151 'callback' => 'restapi_do_it',
152 'callback arguments' => array(array($restapi_item['method'] => $restapi_item)),
153 'access' => $access,
154 'type' => MENU_CALLBACK,
155 'weight' => $restapi_item['weight'],
156 );
157 }
158 }
159 }
160 }
161
162 return array_values($items_by_path);
163 }
164
165 function restapi_do_it($methods) {
166 if (!$methods[ $_SERVER['REQUEST_METHOD'] ]) {
167 return drupal_not_found();
168 }
169
170 $type = $_SERVER['CONTENT_TYPE'];
171 if ($_SERVER['REQUEST_METHOD'] == 'POST' || $_SERVER['REQUEST_METHOD'] == 'PUT') {
172 if ($type == 'application/x-www-form-urlencoded') {
173 $data = $_POST;
174 }
175 elseif ($type == 'multipart/form-data') {
176 $data = $_POST;
177 }
178 elseif ($type == 'application/json') {
179 $input = file_get_contents('php://input');
180 $data = json_decode($input);
181 }
182 else {
183 $input = file_get_contents('php://input');
184 $data = restapi_unserialize($input);
185 }
186 }
187
188 elseif ($_SERVER['REQUEST_METHOD'] == 'GET') {
189 $data = $_GET;
190 }
191
192 else {
193 $data = array();
194 }
195
196 $service = $methods[ $_SERVER['REQUEST_METHOD'] ];
197 $callback = $service['callback'];
198
199 $args = $service['callback arguments'] ? $service['callback arguments']
200 : array_slice(func_get_args(), 1);
201
202 return call_user_func_array($callback, array_merge(array($data), $args));
203 }
204
205 /**
206 * Handles /=/help
207 *
208 * Returns a textual help message describing the REST API.
209 */
210 function restapi_do_help() {
211 header('Content-Type: text/plain; encoding=utf-8');
212 print "Accessing this REST API:\n\n";
213
214 foreach (module_implements('restapi_help') as $module) {
215 $help = module_invoke($module, 'restapi_help');
216
217 if ($help['prefix']) {
218 print $help['prefix'];
219 print "\n\n";
220 }
221
222 foreach ($help['prototypes'] as $prototype) {
223 printf("on %-7s %-40s %s\n",
224 $prototype['method'],
225 $prototype['path'],
226 $prototype['description']
227 );
228 }
229 print "\n";
230
231 if ($help['suffix']) {
232 print $help['suffix'];
233 print "\n\n";
234 }
235 }
236
237 print "Resources are available for return in a variety of formats:
238
239 JSON, JS, PHP, and HTML
240
241 and may be requested in such formats by sending an appropriate HTTP Accept:
242 header or appending one of the extensions to any resource:
243
244 .json, .js, .php
245
246 HTML is output only if the Accept: header or an extension does not request a
247 specific format.
248
249 When your POST or PUT, you may pass data into the REST API using one of the
250 following formats as well. Just make sure the Content-Type: header of the
251 request is set correctly.
252
253 application/x-www-form-urlencoded
254 application/json
255
256 ";
257 return;
258 }
259
260 /**
261 * This method is used to parse the incoming data and place it into an argument
262 * that can be passed into the handlers. This is used in any case where
263 * application/x-www-form-urlencoded data was not used.
264 *
265 * Modules may implement hook_reastapi_unserializer() to perform custom data
266 * conversions. They will be passed the content-type the client claims for the
267 * data and the string containing the data. If the unserializer handles that
268 * kind of data and can decode it, it should return a PHP data structure to be
269 * passed to the service callbacks.
270 *
271 * @param $data the text of the input
272 * @returns a PHP data structure if the data could be decoded, otherwise it
273 * returns FALSE (bad stuff)
274 */
275 function restapi_unserialize($data) {
276 $type = $_SERVER['CONTENT_TYPE'];
277
278 // Allow implementers of the restapi_unserializer to implement new ones
279 foreach (module_implements('restapi_unserializer') as $module) {
280 $output = module_invoke($module, 'restapi_unserializer', $data);
281
282 if ($output) {
283 return $output;
284 }
285 }
286
287 return FALSE;
288 }
289
290 /**
291 * All REST API handlers can use this method to return the result back to the
292 * user in the requested format.
293 *
294 * @param $value some value to include in the returned result
295 */
296 function restapi_serialize($value) {
297 global $REST_API_ACCEPT;
298
299 $return = array(
300 'messages' => drupal_get_messages(),
301 'value' => $value,
302 );
303
304 // Allow implementers of the restapi_serializer to implement new serializers
305 foreach (module_implements('restapi_serializer') as $module) {
306 $output = module_invoke($module, 'restapi_serializer', $return);
307
308 if ($output) {
309 return $output;
310 }
311 }
312
313 // Built-in JSON serializer
314 if (preg_match('/json/i', $REST_API_ACCEPT)) {
315 header('Content-Type: application/json; charset=UTF-8');
316 print json_encode($return);
317 }
318
319 // Built-in JavaScript serializer
320 elseif (preg_match('/j(?:ava)?script|ecmascript/i', $REST_API_ACCEPT)) {
321 header('Content-Type: application/javascript; charset=UTF-8');
322 print 'var $_ = ' . json_encode($return);
323 }
324
325 // Built-in PHP serializer
326 elseif (preg_match('/php/i', $REST_API_ACCEPT)) {
327 header('Content-Type: application/x-php; charset=UTF-8');
328 print '$VAR1 = ' . serialize($return);
329 }
330
331 // Built-in HTML serializer
332 else {
333 header('Content-Type: text/html; charset=UTF-8');
334 print '<html><body>';
335 print restapi_serialize_html($return);
336 print '</body></html>';
337 }
338 }
339
340 /**
341 * This is the fallback serializer that outputs very simple HTML.
342 *
343 * @param $value the value to serialize to HTML
344 */
345 function restapi_serialize_html($value) {
346 $output = '';
347
348 if (is_array($value) || is_object($value)) {
349 $output .= '<dl>';
350 foreach ($value as $key => $value) {
351 $output .= '<dt>' . $key . '</dt>';
352 $output .= '<dd>' . restapi_serialize_html($value) . '</dt>';
353 }
354 $output .= '</dl>';
355 }
356 else {
357 $output .= $value;
358 }
359
360 return $output;
361 }

  ViewVC Help
Powered by ViewVC 1.1.2