1 | <?php |
---|
2 | // $Id: soap_server.module,v 1.2.2.2.2.2.2.10 2011/02/01 14:27:50 rayvaughn Exp $ |
---|
3 | /** |
---|
4 | * @file |
---|
5 | * Enable SOAP for services module (6.x-3.x). |
---|
6 | * |
---|
7 | * The XMLRPC style {resource}.{method} method names have been replaced with |
---|
8 | * {resource}_{method} which are valid PHP function names. |
---|
9 | * |
---|
10 | * The WSDL provides type information for request parameters which is generated from the resource |
---|
11 | * info. Response |
---|
12 | */ |
---|
13 | |
---|
14 | /** |
---|
15 | * Drupal core hooks. |
---|
16 | */ |
---|
17 | |
---|
18 | /** |
---|
19 | * Implementation of hook_menu(). |
---|
20 | */ |
---|
21 | function soap_server_menu() { |
---|
22 | $items['soap_server/debug_wsdl/%soap_server_endpoint'] = array( |
---|
23 | 'type' => MENU_CALLBACK, |
---|
24 | 'title' => 'Soap Server 3 Debug WSDL', |
---|
25 | 'page callback' => 'soap_server_debug_wsdl', |
---|
26 | 'page arguments' => array(2), |
---|
27 | 'access arguments' => array('debug soap server'), |
---|
28 | ); |
---|
29 | $items['soap_server/debug_client/%soap_server_endpoint/%node'] = array( |
---|
30 | 'title' => 'Soap Server Debug Client', |
---|
31 | 'access arguments' => array('debug soap server'), |
---|
32 | 'page callback' => 'soap_server_debug_client', |
---|
33 | 'page arguments' => array(2, 3), |
---|
34 | 'type' => MENU_CALLBACK, |
---|
35 | ); |
---|
36 | return $items; |
---|
37 | } |
---|
38 | |
---|
39 | /** |
---|
40 | * endpoint menu loader |
---|
41 | * |
---|
42 | * Return the services endpoint object from the endpoint name. |
---|
43 | * |
---|
44 | * @param unknown_type $endpoint_name |
---|
45 | */ |
---|
46 | function soap_server_endpoint_load($endpoint_name) { |
---|
47 | $endpoint = services_endpoint_load($endpoint_name); |
---|
48 | if (is_object($endpoint)) { |
---|
49 | return $endpoint; |
---|
50 | } |
---|
51 | return FALSE; |
---|
52 | } |
---|
53 | |
---|
54 | /** |
---|
55 | * Implementation of hook_perm(). |
---|
56 | */ |
---|
57 | function soap_server_perm() { |
---|
58 | return array('access soap server', 'debug soap server'); |
---|
59 | } |
---|
60 | |
---|
61 | /** |
---|
62 | * Implementation of hook_server_info(). |
---|
63 | * |
---|
64 | * This function tells services module that we are providing a server |
---|
65 | */ |
---|
66 | function soap_server_server_info() { |
---|
67 | return array( |
---|
68 | 'name' => 'SOAP', |
---|
69 | ); |
---|
70 | } |
---|
71 | |
---|
72 | /** |
---|
73 | * Services 3 hooks. |
---|
74 | */ |
---|
75 | |
---|
76 | /** |
---|
77 | * Implementation of hook_server(). |
---|
78 | * |
---|
79 | * The services endpoint callback function that handles all requests to a SOAP |
---|
80 | * server 3 endpoint. If ?wsdl is appended to the URL, the WSDL is served. |
---|
81 | */ |
---|
82 | function soap_server_server() { |
---|
83 | $info = services_server_info_object(); |
---|
84 | $endpoint = services_endpoint_load($info->endpoint); |
---|
85 | $get = $_GET; |
---|
86 | // Serve the WSDL if ?wsdl is appended to the URL. |
---|
87 | if (in_array('wsdl', array_keys($get))) { |
---|
88 | // The soap_server_wsdl_output() function delivers the WSDL and exits. |
---|
89 | soap_server_wsdl_output($endpoint); |
---|
90 | } |
---|
91 | // Disable the WSDL cache so it's not stored in memory or on disk. |
---|
92 | //ini_set("soap.wsdl_cache_enabled", "0"); |
---|
93 | $wsdl_url = url($endpoint->path, array('absolute' => TRUE, )) . '?wsdl'; |
---|
94 | |
---|
95 | |
---|
96 | //se configura para que el servidor no compruebe el certificado |
---|
97 | $context = array( |
---|
98 | 'ssl' => array( |
---|
99 | // set some SSL/TLS specific options |
---|
100 | 'verify_peer' => false, |
---|
101 | 'verify_peer_name' => false, |
---|
102 | ), |
---|
103 | ); |
---|
104 | /* |
---|
105 | $options = array( |
---|
106 | 'cache_wsdl' => 0, |
---|
107 | 'trace' => true, |
---|
108 | 'stream_context'=> stream_context_create($context), |
---|
109 | ); |
---|
110 | */ |
---|
111 | $options = array( |
---|
112 | 'cache_wsdl' => 0, |
---|
113 | 'trace' => true, |
---|
114 | 'stream_context'=> stream_context_create($context), |
---|
115 | 'uri' => $wsdl_url, |
---|
116 | ); |
---|
117 | try { |
---|
118 | //$server = new SoapServer($wsdl_url, $options); |
---|
119 | $server = new SoapServer(null, $options); |
---|
120 | $server->setClass(ServicesSoapServer); |
---|
121 | $server->handle(); |
---|
122 | } |
---|
123 | catch (Exception $e) { |
---|
124 | watchdog('soap_server', $e->getMessage(), 'error'); |
---|
125 | } |
---|
126 | exit; |
---|
127 | } |
---|
128 | |
---|
129 | /** |
---|
130 | * Get a WSDL for the given endpoint. |
---|
131 | * |
---|
132 | * The WSDL provides a soap method for each method of the configured resources. |
---|
133 | * See /admin/build/services/your_endpoint/resources |
---|
134 | * |
---|
135 | * @param $endpoint |
---|
136 | */ |
---|
137 | function soap_server_get_wsdl($endpoint) { |
---|
138 | // allow other modules to provide the wsdl |
---|
139 | $wsdl_overrides = module_invoke_all('soap_server_wsdl', $endpoint); |
---|
140 | if (is_array($wsdl_overrides) && !empty($wsdl_overrides)) { |
---|
141 | // if there is more than 1 wsdl the first takes priority allowing modules to override others by |
---|
142 | // setting system weight |
---|
143 | $wsdl = array_shift($wsdl_overrides); |
---|
144 | return $wsdl; |
---|
145 | } |
---|
146 | $service_endpoint = url($endpoint->path, array('absolute' => TRUE)); |
---|
147 | // get the content of the schema for each hose_xml profile using the standard namespace xs: |
---|
148 | $methods = _soap_server_get_methods($endpoint->name); |
---|
149 | foreach ($methods as $method_name => $method_config) { |
---|
150 | // requests can specify which field to use to identify the node - default is nid |
---|
151 | $parts = ""; |
---|
152 | // add parameters from the args array in the resource |
---|
153 | foreach ((array)$method_config['args'] as $arg) { |
---|
154 | switch ($arg['type']) { |
---|
155 | case 'int': |
---|
156 | case 'string': |
---|
157 | case 'struct': |
---|
158 | $parts .= " |
---|
159 | <part name='" . $arg['name'] ."' type='xsd1:". $arg['type'] ."' />"; |
---|
160 | break; |
---|
161 | // we can work out how to deal with other parameter types later |
---|
162 | default: |
---|
163 | $parts .= " |
---|
164 | <part name='". $arg['name'] ."' type='xsd1:any'/>"; |
---|
165 | } |
---|
166 | } |
---|
167 | $requests .= " |
---|
168 | <message name='". $method_name ."_request'>". $parts ." |
---|
169 | </message>"; |
---|
170 | // we don't know what the response will be so go for struct - seems like this is appropriate |
---|
171 | // for most resources |
---|
172 | $responses .= " |
---|
173 | <message name='". $method_name ."_response'> |
---|
174 | <part name='response_object' type='xsd1:struct'/> |
---|
175 | </message>"; |
---|
176 | $port_type_operations .= " |
---|
177 | <operation name='$method_name'> |
---|
178 | <input message='tns:". $method_name ."_request'/> |
---|
179 | <output message='tns:". $method_name ."_response'/> |
---|
180 | </operation>"; |
---|
181 | $binding_operations .= " |
---|
182 | <operation name='$method_name'> |
---|
183 | <soap:operation soapAction='urn:xmethods-delayed-quotes#$method_name'/> |
---|
184 | <input> |
---|
185 | <soap:body |
---|
186 | use='literal' |
---|
187 | namespace='urn:xmethods-delayed-quotes' /> |
---|
188 | </input> |
---|
189 | <output> |
---|
190 | <soap:body |
---|
191 | use='literal' |
---|
192 | namespace='urn:xmethods-delayed-quotes' /> |
---|
193 | </output> |
---|
194 | </operation> |
---|
195 | "; |
---|
196 | } |
---|
197 | $include = drupal_get_path('module', 'soap_server') . '/wsdl/soap_server.wsdl.inc'; |
---|
198 | if (!is_file($include)) { |
---|
199 | return t("Could not load include @inc", array('@inc' => $include)); |
---|
200 | } |
---|
201 | else { |
---|
202 | require_once($include); |
---|
203 | // $wsdl_content is assigned in the include file |
---|
204 | return $wsdl_content; |
---|
205 | } |
---|
206 | } |
---|
207 | |
---|
208 | /** |
---|
209 | * Delivers XML suitable for supplying WSDL to Soap clients. |
---|
210 | * |
---|
211 | * @param $xml |
---|
212 | * The content of the WSDL to serve |
---|
213 | */ |
---|
214 | function soap_server_wsdl_output($endpoint) { |
---|
215 | $wsdl_content = soap_server_get_wsdl($endpoint); |
---|
216 | ob_end_clean(); |
---|
217 | drupal_set_header('Connection: close'); |
---|
218 | drupal_set_header('Content-Length: ' . drupal_strlen($wsdl_content)); |
---|
219 | drupal_set_header('Content-Type: application/wsdl+xml; charset=utf-8'); |
---|
220 | drupal_set_header('Date: '. date('r')); |
---|
221 | echo $wsdl_content; |
---|
222 | exit; |
---|
223 | } |
---|
224 | |
---|
225 | |
---|
226 | /** |
---|
227 | * Soap Server 3 Class for handling soap requests. |
---|
228 | */ |
---|
229 | class ServicesSoapServer { |
---|
230 | public function __call($method_name, $args) { |
---|
231 | // Handle the request. |
---|
232 | $info = services_server_info_object(); |
---|
233 | $endpoint = services_endpoint_load($info->endpoint); |
---|
234 | $services_method_name = str_replace('_soap_', '.', $method_name); |
---|
235 | $controller = services_controller_get($services_method_name, $endpoint->name); |
---|
236 | // make sure any arguments not passed have default values inserted if they are supplied |
---|
237 | // TODO: should we be validating argument types here? |
---|
238 | foreach ($controller['args'] as $key => $arg_config) { |
---|
239 | if (!isset($args[$key]) && isset($arg_config['default value'])) { |
---|
240 | $args[$key] = $arg_config['default value']; |
---|
241 | } |
---|
242 | } |
---|
243 | |
---|
244 | if ($endpoint->debug) { |
---|
245 | watchdog('soap server', "METHOD_NAME:<pre>". print_r($method_name, TRUE)."</pre"); |
---|
246 | watchdog('soap server', "ENDPOINT:<pre>". print_r($endpoint, TRUE)."</pre"); |
---|
247 | watchdog('soap server', "ARGS:<pre>". print_r($args, TRUE)."</pre"); |
---|
248 | watchdog('soap server', "CONTROLLER:<pre>". print_r($controller, TRUE)."</pre"); |
---|
249 | } |
---|
250 | try { |
---|
251 | $ret = services_controller_execute($controller, $args); |
---|
252 | } |
---|
253 | catch (Exception $e) { |
---|
254 | $code = $e->getCode(); |
---|
255 | $soap_fault = new SoapFault($e->getMessage(), $code); |
---|
256 | watchdog('soap_server', $e->getMessage(), 'error'); |
---|
257 | throw $soap_fault; |
---|
258 | } |
---|
259 | return $ret; |
---|
260 | } |
---|
261 | } |
---|
262 | |
---|
263 | /** |
---|
264 | * Return a list of service methods for the endopint |
---|
265 | * Method names are created from {resource}_{method} or {resource}_action_{method} |
---|
266 | * NB: These method names are different from the XMLRPC names because PHP5 can't have a . in function |
---|
267 | * names |
---|
268 | * |
---|
269 | * @param $endpoint |
---|
270 | */ |
---|
271 | function _soap_server_get_methods($endpoint) { |
---|
272 | $resources = services_get_resources($endpoint); |
---|
273 | // traverse the resources retrieving valid methods and action methods |
---|
274 | // TODO: inspect services to confirm the validity of this approach |
---|
275 | foreach ($resources as $resource_name => $resource_info) { |
---|
276 | foreach ($resource_info as $method_name => $method_data) { |
---|
277 | if (!is_array($method_data)) { |
---|
278 | // it's not a method |
---|
279 | continue; |
---|
280 | } |
---|
281 | if ( isset($method_data['callback'])) { |
---|
282 | // if this element has a callback we'll assume it is a method |
---|
283 | $soap_method_name = $resource_name .'_soap_'. $method_name; |
---|
284 | $supported_methods[$soap_method_name] = $method_data; |
---|
285 | } |
---|
286 | if ($method_name == "actions") { |
---|
287 | // TODO: confirm that all actions elements are valid methods |
---|
288 | foreach ($method_data as $action_name => $action_data) { |
---|
289 | $soap_method_name = $resource_name .'_soap_'. $action_name; |
---|
290 | $supported_methods[$soap_method_name] = $action_data; |
---|
291 | } |
---|
292 | } |
---|
293 | } |
---|
294 | } |
---|
295 | return $supported_methods; |
---|
296 | } |
---|
297 | |
---|
298 | /** |
---|
299 | * Debug function for soap_server services. The devel module is required to use |
---|
300 | * this function. |
---|
301 | * |
---|
302 | * @param $nid |
---|
303 | */ |
---|
304 | function soap_server_debug_client($endpoint, $node) { |
---|
305 | if (!module_exists('devel')) { |
---|
306 | drupal_set_message(t('Devel module is required for the debug client function.'), 'error'); |
---|
307 | return t("fail"); |
---|
308 | } |
---|
309 | $debug_output == array( |
---|
310 | 'endpoint' => $endpoint, |
---|
311 | 'node' => $node, |
---|
312 | ); |
---|
313 | if (empty($endpoint)) { |
---|
314 | watchdog('soap_server', 'no endpoint obj in debug client', 'error'); |
---|
315 | return 'fail - no endpoint obj in debug client'; |
---|
316 | } |
---|
317 | ini_set("soap.wsdl_cache_enabled", "0"); // disabling WSDL cache |
---|
318 | $wsdl_url = url($endpoint->path, array('absolute' => TRUE, )) . '?wsdl'; |
---|
319 | try { |
---|
320 | $client = new SoapClient($wsdl_url, array( |
---|
321 | 'trace' => 1 |
---|
322 | )); |
---|
323 | // show the available functions as specified in the WSDL |
---|
324 | $functions = $client->__getFunctions(); |
---|
325 | $debug_output['client functions'] = $functions; |
---|
326 | |
---|
327 | // retrieve a node - need node access for anonymous |
---|
328 | $return = $client->node_soap_retrieve($node->nid); |
---|
329 | $response = $client->__getLastResponse(); |
---|
330 | $request = $client->__getLastRequest(); |
---|
331 | $debug_output["node_soap_retrieve"] = array( |
---|
332 | $node->nid => array('request' => $request, 'response' => $response, 'return' => $return) |
---|
333 | ); |
---|
334 | |
---|
335 | //retrieve a variable - enable services "get a system variable" permission for anonymous |
---|
336 | $return = $client->system_soap_get_variable('css_js_query_string'); |
---|
337 | $response = $client->__getLastResponse(); |
---|
338 | $request = $client->__getLastRequest(); |
---|
339 | $debug_output["system_soap_get_variable"] = array( |
---|
340 | 'css_js_query_string' => array('request' => $request, 'response' => $response, 'return' => $return) |
---|
341 | ); |
---|
342 | |
---|
343 | // retrieve a user - needs acess for anonymous |
---|
344 | $return = $client->user_soap_retrieve(1); // anonymous user needs "access user profiles" perm |
---|
345 | $response = $client->__getLastResponse(); |
---|
346 | $request = $client->__getLastRequest(); |
---|
347 | $debug_output["user_soap_retrieve"] = array( |
---|
348 | 1 => array('request' => $request, 'response' => $response, 'return' => $return) |
---|
349 | ); |
---|
350 | |
---|
351 | } catch (Exception $e) { |
---|
352 | $debug_output['client'] = $e; |
---|
353 | } |
---|
354 | dsm($debug_output); |
---|
355 | return t("done"); |
---|
356 | } |
---|
357 | |
---|
358 | /** |
---|
359 | * Display the content of the WSDL for debugging. |
---|
360 | * |
---|
361 | * @param $endpoint |
---|
362 | */ |
---|
363 | function soap_server_debug_wsdl($endpoint) { |
---|
364 | $wsdl_content = soap_server_get_wsdl($endpoint); |
---|
365 | $wsdl_content = _soap_server_beautify_wsdl($wsdl_content); |
---|
366 | if (module_exists('geshifilter')) { |
---|
367 | $geshi_inc = drupal_get_path('module', 'geshifilter') .'/geshifilter.pages.inc'; |
---|
368 | require_once $geshi_inc; |
---|
369 | $wsdl_content = geshifilter_geshi_process($wsdl_content, 'xml', TRUE); |
---|
370 | } |
---|
371 | else { |
---|
372 | $wsdl_content = "<code>". htmlspecialchars($wsdl_content) ."</code>"; |
---|
373 | } |
---|
374 | return $wsdl_content; |
---|
375 | } |
---|
376 | |
---|
377 | /** |
---|
378 | * Make the WSDL look nice. |
---|
379 | * |
---|
380 | * WARNING: This is NOT a general XML formatter because it will remove whitespace |
---|
381 | * between tags and in CDATA blocks |
---|
382 | * |
---|
383 | * @param unknown_type $xml |
---|
384 | */ |
---|
385 | function _soap_server_beautify_wsdl($xml) { |
---|
386 | // remove whitespace between tags |
---|
387 | $xml = preg_replace('/(>)(\s*)(<)/', '$1$3', $xml); |
---|
388 | // limit spaces and tabs one space |
---|
389 | $xml = preg_replace('/([ \t]{2,})/', ' ', $xml); |
---|
390 | // add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries) |
---|
391 | $xml = preg_replace('/(>)\s*(<)(\/*)/', "$1\n$2$3", $xml); |
---|
392 | |
---|
393 | // now indent the tags |
---|
394 | $token = strtok($xml, "\n"); |
---|
395 | $result = ''; // holds formatted version as it is built |
---|
396 | $pad = 1; // initial indent |
---|
397 | $matches = array(); // returns from preg_matches() |
---|
398 | |
---|
399 | // scan each line and adjust indent based on opening/closing tags |
---|
400 | while ($token !== false) : |
---|
401 | |
---|
402 | // test for the various tag states |
---|
403 | |
---|
404 | // 1. open and closing tags on same line - no change |
---|
405 | if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)) : |
---|
406 | $indent=0; |
---|
407 | // 2. closing tag - outdent now |
---|
408 | elseif (preg_match('/^<\/\w/', $token, $matches)) : |
---|
409 | $pad--; |
---|
410 | // 3. opening tag - don't pad this one, only subsequent tags |
---|
411 | elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches)) : |
---|
412 | $indent=1; |
---|
413 | // 4. no indentation needed |
---|
414 | else : |
---|
415 | $indent = 0; |
---|
416 | endif; |
---|
417 | |
---|
418 | // pad the line with the required number of leading spaces |
---|
419 | $line = str_pad($token, strlen($token)+$pad, ' ', STR_PAD_LEFT); |
---|
420 | $result .= $line . "\n"; // add to the cumulative result, with linefeed |
---|
421 | $token = strtok("\n"); // get the next token |
---|
422 | $pad += $indent; // update the pad size for subsequent lines |
---|
423 | endwhile; |
---|
424 | |
---|
425 | return $result; |
---|
426 | } |
---|