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

Last change on this file since 1781 was 1781, checked in by rvelices, 17 years ago

web services:

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