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

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

merge r2052 from branch-1_7 to trunk

  • fix set_status_header for fastCGI installations that are strict in terms of http protocol (1.0 or 1.1)
  • Property svn:eol-style set to LF
  • Property svn:keywords set to Author Date Id Revision
File size: 17.8 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 2053 2007-07-07 02:10:11Z rvelices $
7// | last update   : $Date: 2007-07-07 02:10:11 +0000 (Sat, 07 Jul 2007) $
8// | last modifier : $Author: rvelices $
9// | revision      : $Revision: 2053 $
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: 2053 $
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      set_status_header(500);
384      @header("Content-Type: text/plain");
385      echo ("Cannot process your request. Unknown response format.
386Request format: ".@$this->_requestFormat." handler:".$this->_requestHandler."
387Response format: ".@$this->_responseFormat." encoder:".$this->_responseEncoder."
388    ");
389      var_export($this);
390      die(0);
391    }
392
393    if ( is_null($this->_requestHandler) )
394    {
395      $this->sendResponse(
396        new PwgError(500, 'Unknown request format')
397        );
398      return;
399    }
400
401    $this->addMethod('reflection.getMethodList',
402        array('PwgServer', 'ws_getMethodList'),
403        null, '' );
404    $this->addMethod('reflection.getMethodDetails',
405        array('PwgServer', 'ws_getMethodDetails'),
406        array('methodName'),'');
407
408    trigger_action('ws_add_methods', array(&$this) );
409    uksort( $this->_methods, 'strnatcmp' );
410    $this->_requestHandler->handleRequest($this);
411  }
412
413  /**
414   * Encodes a response and sends it back to the browser.
415   */
416  function sendResponse($response)
417  {
418    $encodedResponse = $this->_responseEncoder->encodeResponse($response);
419    $contentType = $this->_responseEncoder->getContentType();
420
421    @header('Content-Type: '.$contentType);
422    print_r($encodedResponse);
423  }
424
425  /**
426   * Registers a web service method.
427   * @param methodName string - the name of the method as seen externally
428   * @param callback mixed - php method to be invoked internally
429   * @param params array - map of allowed parameter names with optional default
430   * values and parameter flags. Example of $params:
431   * array( 'param1' => array('default'=>523, 'flags'=>WS_PARAM_FORCE_ARRAY) ) .
432   * Possible parameter flags are:
433   * WS_PARAM_ALLOW_ARRAY - this parameter can be an array
434   * WS_PARAM_FORCE_ARRAY - if this parameter is scalar, force it to an array
435   *  before invoking the method
436   * @param description string - a description of the method.
437   */
438  function addMethod($methodName, $callback, $params=array(), $description, $include_file='')
439  {
440    $this->_methods[$methodName] = $callback;
441    $this->_methodDescriptions[$methodName] = $description;
442
443    if (!is_array($params))
444    {
445      $params = array();
446    }
447
448    if ( range(0, count($params) - 1) === array_keys($params) )
449    {
450      $params = array_flip($params);
451    }
452
453    foreach( $params as $param=>$options)
454    {
455      if ( !is_array($options) )
456      {
457        $params[$param] = array('flags'=>0);
458      }
459      else
460      {
461        $flags = isset($options['flags']) ? $options['flags'] : 0;
462        if ( array_key_exists('default', $options) )
463        {
464          $flags |= WS_PARAM_OPTIONAL;
465        }
466        if ( $flags & WS_PARAM_FORCE_ARRAY )
467        {
468          $flags |= WS_PARAM_ACCEPT_ARRAY;
469        }
470        $options['flags'] = $flags;
471        $params[$param] = $options;
472      }
473    }
474    $this->_methodSignatures[$methodName] = $params;
475  }
476
477  function hasMethod($methodName)
478  {
479    return isset($this->_methods[$methodName]);
480  }
481
482  function getMethodDescription($methodName)
483  {
484    $desc = @$this->_methodDescriptions[$methodName];
485    return isset($desc) ? $desc : '';
486  }
487
488  function getMethodSignature($methodName)
489  {
490    $signature = @$this->_methodSignatures[$methodName];
491    return isset($signature) ? $signature : array();
492  }
493
494  /*static*/ function isPost()
495  {
496    return isset($HTTP_RAW_POST_DATA) or !empty($_POST);
497  }
498
499  /*static*/ function makeArrayParam(&$param)
500  {
501    if ( $param==null )
502    {
503      $param = array();
504    }
505    else
506    {
507      if (! is_array($param) )
508      {
509        $param = array($param);
510      }
511    }
512  }
513
514  /**
515   *  Invokes a registered method. Returns the return of the method (or
516   *  a PwgError object if the method is not found)
517   *  @param methodName string the name of the method to invoke
518   *  @param params array array of parameters to pass to the invoked method
519   */
520  function invoke($methodName, $params)
521  {
522    $callback = @$this->_methods[$methodName];
523
524    if ( $callback==null )
525    {
526      return new PwgError(WS_ERR_INVALID_METHOD, 'Method name "'.$methodName.'" is not valid');
527    }
528
529    // parameter check and data coercion !
530    $signature = @$this->_methodSignatures[$methodName];
531    $missing_params = array();
532    foreach($signature as $name=>$options)
533    {
534      $flags = $options['flags'];
535      if ( !array_key_exists($name, $params) )
536      {// parameter not provided in the request
537        if ( !($flags&WS_PARAM_OPTIONAL) )
538        {
539          $missing_params[] = $name;
540        }
541        else if ( array_key_exists('default',$options) )
542        {
543          $params[$name] = $options['default'];
544          if ( ($flags&WS_PARAM_FORCE_ARRAY) )
545          {
546            $this->makeArrayParam( $params[$name] );
547          }
548        }
549      }
550      else
551      {// parameter provided - do some basic checks
552        $the_param = $params[$name];
553        if ( is_array($the_param) and ($flags&WS_PARAM_ACCEPT_ARRAY)==0 )
554        {
555          return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be scalar' );
556        }
557        if ( ($flags&WS_PARAM_FORCE_ARRAY) )
558        {
559          $this->makeArrayParam( $the_param );
560        }
561        if ( isset($options['maxValue']) and $the_param>$options['maxValue'])
562        {
563          $the_param = $options['maxValue'];
564        }
565        $params[$name] = $the_param;
566      }
567    }
568    if (count($missing_params))
569    {
570      return new PwgError(WS_ERR_MISSING_PARAM, 'Missing parameters: '.implode(',',$missing_params));
571    }
572    $result = trigger_event('ws_invoke_allowed', true, $methodName, $params);
573    if ( strtolower( get_class($result) )!='pwgerror')
574    {
575      $result = call_user_func_array($callback, array($params, &$this) );
576    }
577    return $result;
578  }
579
580  /**
581   * WS reflection method implementation: lists all available methods
582   */
583  /*static*/ function ws_getMethodList($params, &$service)
584  {
585    return array('methods' => new PwgNamedArray( array_keys($service->_methods),'method' ) );
586  }
587
588  /**
589   * WS reflection method implementation: gets information about a given method
590   */
591  /*static*/ function ws_getMethodDetails($params, &$service)
592  {
593    $methodName = $params['methodName'];
594    if (!$service->hasMethod($methodName))
595    {
596      return new PwgError(WS_ERR_INVALID_PARAM,
597            'Requested method does not exist');
598    }
599    $res = array(
600      'name' => $methodName,
601      'description' => $service->getMethodDescription($methodName),
602      'params' => array(),
603    );
604    $signature = $service->getMethodSignature($methodName);
605    foreach ($signature as $name => $options)
606    {
607      $param_data = array(
608        'name' => $name,
609        'optional' => ($options['flags']&WS_PARAM_OPTIONAL)?true:false,
610        'acceptArray' => ($options['flags']&WS_PARAM_ACCEPT_ARRAY)?true:false,
611        );
612      if (isset($options['default']))
613      {
614        $param_data['defaultValue'] = $options['default'];
615      }
616      $res['params'][] = $param_data;
617    }
618    return $res;
619  }
620}
621?>
Note: See TracBrowser for help on using the repository browser.