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

Last change on this file since 14086 was 12922, checked in by mistic100, 13 years ago

update Piwigo headers to 2012, last change before the expected (or not) apocalypse

  • 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-2012 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.