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

Last change on this file since 25025 was 25025, checked in by mistic100, 11 years ago

add "abstract" keyword for API base classes + PwgRestRequestHandler inherits from PwgRequestHandler

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