source: trunk/include/ws_core.inc.php @ 1730

Last change on this file since 1730 was 1698, checked in by rvelices, 18 years ago

Web service first version.

  • Property svn:eol-style set to native
File size: 17.6 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | PhpWebGallery - a PHP based picture gallery                           |
4// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net |
5// +-----------------------------------------------------------------------+
6// | branch        : BSF (Best So Far)
7// | file          : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $
8// | last update   : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $
9// | last modifier : $Author: rvelices $
10// | revision      : $Rev: 1678 $
11// +-----------------------------------------------------------------------+
12// | This program is free software; you can redistribute it and/or modify  |
13// | it under the terms of the GNU General Public License as published by  |
14// | the Free Software Foundation                                          |
15// |                                                                       |
16// | This program is distributed in the hope that it will be useful, but   |
17// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
18// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
19// | General Public License for more details.                              |
20// |                                                                       |
21// | You should have received a copy of the GNU General Public License     |
22// | along with this program; if not, write to the Free Software           |
23// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
24// | USA.                                                                  |
25// +-----------------------------------------------------------------------+
26
27/**** WEB SERVICE CORE CLASSES************************************************
28 * PwgServer - main object - the link between web service methods, request
29 *  handler and response encoder
30 * PwgRequestHandler - base class for handlers
31 * PwgResponseEncoder - base class for response encoders
32 * PwgError, PwgNamedArray, PwgNamedStruct - can be used by web service functions
33 * as return values
34 */
35
36
37define( 'WS_PARAM_ACCEPT_ARRAY',  0x010000 );
38define( 'WS_PARAM_FORCE_ARRAY',   0x030000 );
39define( 'WS_PARAM_OPTIONAL',      0x040000 );
40
41define( 'WS_ERR_INVALID_METHOD',  1001 );
42define( 'WS_ERR_MISSING_PARAM',   1002 );
43define( 'WS_ERR_INVALID_PARAM',   1003 );
44
45define( 'WS_XML_ATTRIBUTES', 'attributes_xml_');
46define( 'WS_XML_CONTENT', 'content_xml_');
47
48/**
49 * PwgError object can be returned from any web service function implementation.
50 */
51class PwgError
52{
53  var $_code;
54  var $_codeText;
55
56  function PwgError($code, $codeText)
57  {
58    $this->_code = $code;
59    $this->_codeText = $codeText;
60  }
61
62  function code() { return $this->_code; }
63  function message() { return $this->_codeText; }
64}
65
66/**
67 * Simple wrapper around an array (keys are consecutive integers starting at 0).
68 * Provides naming clues for xml output (xml attributes vs. xml child elements?)
69 * Usually returned by web service function implementation.
70 */
71class PwgNamedArray
72{
73  /*private*/ var $_content;
74  /*private*/ var $_itemName;
75  /*private*/ var $_xmlAttributes;
76
77  /**
78   * Constructs a named array
79   * @param arr array (keys must be consecutive integers starting at 0)
80   * @param itemName string xml element name for values of arr (e.g. image)
81   * @param xmlAttributes array of sub-item attributes that will be encoded as
82   *      xml attributes instead of xml child elements
83   */
84  function PwgNamedArray(&$arr, $itemName, $xmlAttributes=array() )
85  {
86    $this->_content = $arr;
87    $this->_itemName = $itemName;
88    $this->_xmlAttributes = array_flip($xmlAttributes);
89  }
90}
91/**
92 * Simple wrapper around a "struct" (php array whose keys are not consecutive
93 * integers starting at 0). Provides naming clues for xml output (what is xml
94 * attributes and what is element)
95 */
96class PwgNamedStruct
97{
98  /*private*/ var $_content;
99  /*private*/ var $_name;
100  /*private*/ var $_xmlAttributes;
101
102  /**
103   * Constructs a named struct (usually returned by web service function
104   * implementation)
105   * @param name string - containing xml element name
106   * @param content array - the actual content (php array)
107   * @param xmlAttributes array - name of the keys in $content that will be
108   *    encoded as xml attributes (if null - automatically prefer xml attributes
109   *    whenever possible)
110   */
111  function PwgNamedStruct($name, $content, $xmlAttributes=null, $xmlElements=null )
112  {
113    $this->_name = $name;
114    $this->_content = $content;
115    if ( isset($xmlAttributes) )
116    {
117      $this->_xmlAttributes = array_flip($xmlAttributes);
118    }
119    else
120    {
121      $this->_xmlAttributes = array();
122      foreach ($this->_content as $key=>$value)
123      {
124        if (!empty($key) and (is_scalar($value) or is_null($value)) )
125        {
126          if ( empty($xmlElements) or !in_array($key,$xmlElements) )
127          {
128            $this->_xmlAttributes[$key]=1;
129          }
130        }
131      }
132    }
133  }
134}
135
136
137/**
138 * Replace array_walk_recursive()
139 *
140 * @category    PHP
141 * @package     PHP_Compat
142 * @link        http://php.net/function.array_walk_recursive
143 * @author      Tom Buskens <ortega@php.net>
144 * @author      Aidan Lister <aidan@php.net>
145 * @version     $Revision: 1.7 $
146 * @since       PHP 5
147 * @require     PHP 4.0.6 (is_callable)
148 */
149if (!function_exists('array_walk_recursive')) {
150    function array_walk_recursive(&$input, $funcname)
151    {
152        if (!is_callable($funcname)) {
153            if (is_array($funcname)) {
154                $funcname = $funcname[0] . '::' . $funcname[1];
155            }
156            user_error('array_walk_recursive() Not a valid callback ' . $user_func,
157                E_USER_WARNING);
158            return;
159        }
160
161        if (!is_array($input)) {
162            user_error('array_walk_recursive() The argument should be an array',
163                E_USER_WARNING);
164            return;
165        }
166
167        $args = func_get_args();
168
169        foreach ($input as $key => $item) {
170            if (is_array($item)) {
171                array_walk_recursive($item, $funcname, $args);
172                $input[$key] = $item;
173            } else {
174                $args[0] = &$item;
175                $args[1] = &$key;
176                call_user_func_array($funcname, $args);
177                $input[$key] = $item;
178            }
179        }
180    }
181}
182
183/**
184 * Abstract base class for request handlers.
185 */
186class PwgRequestHandler
187{
188  /** Virtual abstract method. Decodes the request (GET or POST) handles the
189   * method invocation as well as response sending.
190   */
191  function handleRequest(&$server) { assert(false); }
192}
193
194/**
195 *
196 * Base class for web service response encoder.
197 */
198class PwgResponseEncoder
199{
200  /** encodes the web service response to the appropriate output format
201   * @param response mixed the unencoded result of a service method call
202   */
203  function encodeResponse($response) { assert(false); }
204
205  /** default "Content-Type" http header for this kind of response format
206   */
207  function getContentType() { assert(false); }
208
209  /**
210   * returns true if the parameter is a 'struct' (php array type whose keys are
211   * NOT consecutive integers starting with 0)
212   */
213  function is_struct(&$data)
214  {
215    if (is_array($data) )
216    {
217      if (range(0, count($data) - 1) !== array_keys($data) )
218      { # string keys, unordered, non-incremental keys, .. - whatever, make object
219        return true;
220      }
221    }
222    return false;
223  }
224
225  /**
226   * removes all XML formatting from $response (named array, named structs, etc)
227   * usually called by every response encoder, except rest xml.
228   */
229  function flattenResponse(&$response)
230  {
231    PwgResponseEncoder::_mergeAttributesAndContent($response);
232    PwgResponseEncoder::_removeNamedArray($response);
233    PwgResponseEncoder::_removeNamedStruct($response);
234    if (is_array($response))
235    { // need to call 2 times (first time might add new arrays)
236      array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') );
237      array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') );
238    }
239//print_r($response);
240    PwgResponseEncoder::_mergeAttributesAndContent($response);
241  }
242
243  /*private*/ function _remove_named_callback(&$value, $key)
244  {
245    do
246    {
247      $changed = 0;
248      $changed += PwgResponseEncoder::_removeNamedArray($value);
249      $changed += PwgResponseEncoder::_removeNamedStruct($value);
250  //    print_r('walk '.$key."<br/>\n");
251    }
252    while ($changed);
253  }
254
255  /*private*/ function _mergeAttributesAndContent(&$value)
256  {
257    if ( !is_array($value) )
258      return;
259/*    $first_key = '';
260    if (count($value)) { $ak = array_keys($value); $first_key = $ak[0]; }
261
262    print_r( '_mergeAttributesAndContent is_struct='.PwgResponseEncoder::is_struct($value)
263      .' count='.count($value)
264      .' first_key='.$first_key
265      ."<br/>\n"
266      );*/
267    $ret = 0;
268    if (PwgResponseEncoder::is_struct($value))
269    {
270      if ( isset($value[WS_XML_ATTRIBUTES]) )
271      {
272        $value = array_merge( $value, $value[WS_XML_ATTRIBUTES] );
273        unset( $value[WS_XML_ATTRIBUTES] );
274        $ret=1;
275      }
276      if ( isset($value[WS_XML_CONTENT]) )
277      {
278        $cont_processed = 0;
279        if ( count($value)==1 )
280        {
281          $value = $value[WS_XML_CONTENT];
282          $cont_processed=1;
283        }
284        else
285        {
286          if (PwgResponseEncoder::is_struct($value[WS_XML_CONTENT]))
287          {
288            $value = array_merge( $value, $value[WS_XML_CONTENT] );
289            unset( $value[WS_XML_CONTENT] );
290            $cont_processed=1;
291          }
292        }
293        $ret += $cont_processed;
294        if (!$cont_processed)
295        {
296          $value['_content'] = $value[WS_XML_CONTENT];
297          unset( $value[WS_XML_CONTENT] );
298          $ret++;
299        }
300      }
301    }
302
303    foreach ($value as $key=>$v)
304    {
305      if ( PwgResponseEncoder::_mergeAttributesAndContent($v) )
306      {
307        $value[$key]=$v;
308        $ret++;
309      }
310    }
311    return $ret;
312  }
313
314  /*private*/ function _removeNamedArray(&$value)
315  {
316    if ( strtolower( get_class($value) ) =='pwgnamedarray')
317    {
318      $value = $value->_content;
319      return 1;
320    }
321    return 0;
322  }
323
324  /*private*/ function _removeNamedStruct(&$value)
325  {
326    if ( strtolower( get_class($value) ) =='pwgnamedstruct')
327    {
328      if ( isset($value->_content['']) )
329      {
330        $unknown = $value->_content[''];
331        unset( $value->_content[''] );
332        $value->_content[$value->_name] = $unknown;
333      }
334      $value = $value->_content;
335      return 1;
336    }
337    return 0;
338  }
339}
340
341
342
343class PwgServer
344{
345  var $_requestHandler;
346  var $_requestFormat;
347  var $_responseEncoder;
348  var $_responseFormat;
349
350  var $_methods;
351  var $_methodSignatures;
352
353  function PwgServer()
354  {
355    $methods = array();
356  }
357
358  /**
359   *  Initializes the request handler.
360   */
361  function setHandler($requestFormat, &$requestHandler)
362  {
363    $this->_requestHandler = &$requestHandler;
364    $this->_requestFormat = $requestFormat;
365  }
366
367  /**
368   *  Initializes the request handler.
369   */
370  function setEncoder($responseFormat, &$encoder)
371  {
372    $this->_responseEncoder = &$encoder;
373    $this->_responseFormat = $responseFormat;
374  }
375
376  /**
377   * Runs the web service call (handler and response encoder should have been
378   * created)
379   */
380  function run()
381  {
382    if ( is_null($this->_responseEncoder) )
383    {
384      @header("HTTP/1.1 500 Server error");
385      @header("Status: 500 Server error");
386      @header("Content-Type: text/plain");
387      echo ("Cannot process your request. Unknown response format.
388Request format: ".@$this->_requestFormat." handler:".$this->_requestHandler."
389Response format: ".@$this->_responseFormat." encoder:".$this->_responseEncoder."
390    ");
391      var_export($this);
392      die(0);
393    }
394
395    if ( is_null($this->_requestHandler) )
396    {
397      $this->sendResponse(
398        new PwgError(500, 'Unknown request format')
399        );
400      return;
401    }
402
403    $this->addMethod('reflection.getMethodList',
404        array('PwgServer', 'ws_getMethodList'),
405        null, '' );
406    $this->addMethod('reflection.getMethodDetails',
407        array('PwgServer', 'ws_getMethodDetails'),
408        array('methodName'),'');
409
410    trigger_action('ws_add_methods', array(&$this) );
411    uksort( $this->_methods, 'strnatcmp' );
412    $this->_requestHandler->handleRequest($this);
413  }
414
415  /**
416   * Encodes a response and sends it back to the browser.
417   */
418  function sendResponse($response)
419  {
420    $encodedResponse = $this->_responseEncoder->encodeResponse($response);
421    $contentType = $this->_responseEncoder->getContentType();
422
423    @header('Content-Type: '.$contentType);
424    print_r($encodedResponse);
425  }
426
427  /**
428   * Registers a web service method.
429   * @param methodName string - the name of the method as seen externally
430   * @param callback mixed - php method to be invoked internally
431   * @param params array - map of allowed parameter names with optional default
432   * values and parameter flags. Example of $params:
433   * array( 'param1' => array('default'=>523, 'flags'=>WS_PARAM_FORCE_ARRAY) ) .
434   * Possible parameter flags are:
435   * WS_PARAM_ALLOW_ARRAY - this parameter can be an array
436   * WS_PARAM_FORCE_ARRAY - if this parameter is scalar, force it to an array
437   *  before invoking the method
438   * @param description string - a description of the method.
439   */
440  function addMethod($methodName, $callback, $params=array(), $description, $include_file='')
441  {
442    $this->_methods[$methodName] = $callback;
443    $this->_methodDescriptions[$methodName] = $description;
444
445    if (!is_array($params))
446    {
447      $params = array();
448    }
449
450    if ( range(0, count($params) - 1) === array_keys($params) )
451    {
452      $params = array_flip($params);
453    }
454
455    foreach( $params as $param=>$options)
456    {
457      if ( !is_array($options) )
458      {
459        $params[$param] = array('flags'=>0);
460      }
461      else
462      {
463        $flags = isset($options['flags']) ? $options['flags'] : 0;
464        if ( array_key_exists('default', $options) )
465        {
466          $flags |= WS_PARAM_OPTIONAL;
467        }
468        $options['flags'] = $flags;
469        $params[$param] = $options;
470      }
471    }
472    $this->_methodSignatures[$methodName] = $params;
473  }
474
475  function hasMethod($methodName)
476  {
477    return isset($this->_methods[$methodName]);
478  }
479
480  function getMethodDescription($methodName)
481  {
482    $desc = @$this->_methodDescriptions[$methodName];
483    return isset($desc) ? $desc : '';
484  }
485
486  function getMethodSignature($methodName)
487  {
488    $signature = @$this->_methodSignatures[$methodName];
489    return isset($signature) ? $signature : array();
490  }
491
492  /*static*/ function isPost()
493  {
494    return isset($HTTP_RAW_POST_DATA) or !empty($_POST);
495  }
496
497  /*static*/ function makeArrayParam(&$param)
498  {
499    if ( $param==null )
500    {
501      $param = array();
502    }
503    else
504    {
505      if (! is_array($param) )
506      {
507        $param = array($param);
508      }
509    }
510  }
511
512  /**
513   *  Invokes a registered method. Returns the return of the method (or
514   *  a PwgError object if the method is not found)
515   *  @param methodName string the name of the method to invoke
516   *  @param params array array of parameters to pass to the invoked method
517   */
518  function invoke($methodName, $params)
519  {
520    $callback = @$this->_methods[$methodName];
521
522    if ( $callback==null )
523    {
524      return new PwgError(WS_ERR_INVALID_METHOD, 'Method name "'.$methodName.'" is not valid');
525    }
526
527    // parameter check and data coercion !
528    $signature = @$this->_methodSignatures[$methodName];
529    $missing_params = array();
530    foreach($signature as $name=>$options)
531    {
532      $flags = $options['flags'];
533      if ( !array_key_exists($name, $params) )
534      {// parameter not provided in the request
535        if ( !($flags&WS_PARAM_OPTIONAL) )
536        {
537          $missing_params[] = $name;
538        }
539        else if ( array_key_exists('default',$options) )
540        {
541          $params[$name] = $options['default'];
542          if ( ($flags&WS_PARAM_FORCE_ARRAY) )
543          {
544            $this->makeArrayParam( $params[$name] );
545          }
546        }
547      }
548      else
549      {// parameter provided - do some basic checks
550        $the_param = $params[$name];
551        if ( is_array($the_param) and ($flags&WS_PARAM_ACCEPT_ARRAY)==0 )
552        {
553          return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be scalar' );
554        }
555        if ( ($flags&WS_PARAM_FORCE_ARRAY) )
556        {
557          $this->makeArrayParam( $the_param );
558        }
559        $params[$name] = $the_param;
560      }
561    }
562    if (count($missing_params))
563    {
564      return new PwgError(WS_ERR_MISSING_PARAM, 'Missing parameters: '.implode(',',$missing_params));
565    }
566
567    $result = call_user_func_array($callback, array($params, &$this) );
568    return $result;
569  }
570
571  /**
572   * WS reflection method implementation: lists all available methods
573   */
574  /*static*/ function ws_getMethodList($params, &$service)
575  {
576    return array('methods' => new PwgNamedArray( array_keys($service->_methods),'method' ) );
577  }
578
579  /**
580   * WS reflection method implementation: gets information about a given method
581   */
582  /*static*/ function ws_getMethodDetails($params, &$service)
583  {
584    $methodName = $params['methodName'];
585    if (!$service->hasMethod($methodName))
586    {
587      return new PwgError(WS_ERR_INVALID_PARAM,
588            'Requested method does not exist');
589    }
590    $res = array(
591      'name' => $methodName,
592      'description' => $service->getMethodDescription($methodName),
593      'params' => array(),
594    );
595    $signature = $service->getMethodSignature($methodName);
596    foreach ($signature as $name => $options)
597    {
598      $param_data = array(
599        'name' => $name,
600        'optional' => ($options['flags']&WS_PARAM_OPTIONAL)?true:false,
601        );
602      if (isset($options['default']))
603      {
604        $param_data['defaultValue'] = $options['default'];
605      }
606      $res['params'][] = $param_data;
607    }
608    return $res;
609  }
610}
611?>
Note: See TracBrowser for help on using the repository browser.