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

Last change on this file since 20516 was 19703, checked in by plg, 12 years ago

update Piwigo headers to 2013 (the end of the world didn't occur as expected on r12922)

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