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

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

Bug fixed: as rvelices notified me by email, my header replacement script was
bugged (r2297 was repeating new and old header).

By the way, I've also removed the replacement keywords. We were using them
because it was a common usage with CVS but it is advised not to use them with
Subversion. Personnaly, it is a problem when I search differences between 2
Piwigo installations outside Subversion.

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