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

Last change on this file since 2297 was 2297, checked in by plg, 16 years ago

Modification: new header on PHP files, PhpWebGallery renamed Piwigo.

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