source: tags/build-Butterfly02/include/ws_core.inc.php @ 2524

Last change on this file since 2524 was 2521, checked in by rvelices, 16 years ago
  • images.file categories.permalink old_permalinks.permalink - become binary
  • session security improvement: now the sessions are valid only for originating ip addr (with mask 255.255.0.0 to allow users behind load balancing proxies) -> stealing the session cookie is almost a non issue (with the exception of the 65536 machines in range)
  • metadata sync from the sync button does not overwrite valid data with empty metadata
  • other small fixes/enhancements:
    • added event get_category_image_orders
    • fix display issue with redirect.tpl (h1/h2 within h1)
    • fix known_script smarty function registration
    • query search form not submitted if q is empty
    • better admin css rules
    • some other minor changes (ws_core, rest_handler, functions_search...)
  • 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    set_status_header($code, $codeText);
56
57    $this->_code = $code;
58    $this->_codeText = $codeText;
59  }
60
61  function code() { return $this->_code; }
62  function message() { return $this->_codeText; }
63}
64
65/**
66 * Simple wrapper around an array (keys are consecutive integers starting at 0).
67 * Provides naming clues for xml output (xml attributes vs. xml child elements?)
68 * Usually returned by web service function implementation.
69 */
70class PwgNamedArray
71{
72  /*private*/ var $_content;
73  /*private*/ var $_itemName;
74  /*private*/ var $_xmlAttributes;
75
76  /**
77   * Constructs a named array
78   * @param arr array (keys must be consecutive integers starting at 0)
79   * @param itemName string xml element name for values of arr (e.g. image)
80   * @param xmlAttributes array of sub-item attributes that will be encoded as
81   *      xml attributes instead of xml child elements
82   */
83  function PwgNamedArray(&$arr, $itemName, $xmlAttributes=array() )
84  {
85    $this->_content = $arr;
86    $this->_itemName = $itemName;
87    $this->_xmlAttributes = array_flip($xmlAttributes);
88  }
89}
90/**
91 * Simple wrapper around a "struct" (php array whose keys are not consecutive
92 * integers starting at 0). Provides naming clues for xml output (what is xml
93 * attributes and what is element)
94 */
95class PwgNamedStruct
96{
97  /*private*/ var $_content;
98  /*private*/ var $_name;
99  /*private*/ var $_xmlAttributes;
100
101  /**
102   * Constructs a named struct (usually returned by web service function
103   * implementation)
104   * @param name string - containing xml element name
105   * @param content array - the actual content (php array)
106   * @param xmlAttributes array - name of the keys in $content that will be
107   *    encoded as xml attributes (if null - automatically prefer xml attributes
108   *    whenever possible)
109   */
110  function PwgNamedStruct($name, $content, $xmlAttributes=null, $xmlElements=null )
111  {
112    $this->_name = $name;
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 * Replace array_walk_recursive()
138 *
139 * @category    PHP
140 * @package     PHP_Compat
141 * @link        http://php.net/function.array_walk_recursive
142 * @author      Tom Buskens <ortega@php.net>
143 * @author      Aidan Lister <aidan@php.net>
144 * @version     $Revision: 2521 $
145 * @since       PHP 5
146 * @require     PHP 4.0.6 (is_callable)
147 */
148if (!function_exists('array_walk_recursive')) {
149    function array_walk_recursive(&$input, $funcname)
150    {
151        if (!is_callable($funcname)) {
152            if (is_array($funcname)) {
153                $funcname = $funcname[0] . '::' . $funcname[1];
154            }
155            user_error('array_walk_recursive() Not a valid callback ' . $user_func,
156                E_USER_WARNING);
157            return;
158        }
159
160        if (!is_array($input)) {
161            user_error('array_walk_recursive() The argument should be an array',
162                E_USER_WARNING);
163            return;
164        }
165
166        $args = func_get_args();
167
168        foreach ($input as $key => $item) {
169            if (is_array($item)) {
170                array_walk_recursive($item, $funcname, $args);
171                $input[$key] = $item;
172            } else {
173                $args[0] = &$item;
174                $args[1] = &$key;
175                call_user_func_array($funcname, $args);
176                $input[$key] = $item;
177            }
178        }
179    }
180}
181
182/**
183 * Abstract base class for request handlers.
184 */
185class PwgRequestHandler
186{
187  /** Virtual abstract method. Decodes the request (GET or POST) handles the
188   * method invocation as well as response sending.
189   */
190  function handleRequest(&$server) { assert(false); }
191}
192
193/**
194 *
195 * Base class for web service response encoder.
196 */
197class PwgResponseEncoder
198{
199  /** encodes the web service response to the appropriate output format
200   * @param response mixed the unencoded result of a service method call
201   */
202  function encodeResponse($response) { assert(false); }
203
204  /** default "Content-Type" http header for this kind of response format
205   */
206  function getContentType() { assert(false); }
207
208  /**
209   * returns true if the parameter is a 'struct' (php array type whose keys are
210   * NOT consecutive integers starting with 0)
211   */
212  function is_struct(&$data)
213  {
214    if (is_array($data) )
215    {
216      if (range(0, count($data) - 1) !== array_keys($data) )
217      { # string keys, unordered, non-incremental keys, .. - whatever, make object
218        return true;
219      }
220    }
221    return false;
222  }
223
224  /**
225   * removes all XML formatting from $response (named array, named structs, etc)
226   * usually called by every response encoder, except rest xml.
227   */
228  function flattenResponse(&$response)
229  {
230    PwgResponseEncoder::_mergeAttributesAndContent($response);
231    PwgResponseEncoder::_removeNamedArray($response);
232    PwgResponseEncoder::_removeNamedStruct($response);
233    if (is_array($response))
234    { // need to call 2 times (first time might add new arrays)
235      array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') );
236      array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') );
237    }
238//print_r($response);
239    PwgResponseEncoder::_mergeAttributesAndContent($response);
240  }
241
242  /*private*/ function _remove_named_callback(&$value, $key)
243  {
244    do
245    {
246      $changed = 0;
247      $changed += PwgResponseEncoder::_removeNamedArray($value);
248      $changed += PwgResponseEncoder::_removeNamedStruct($value);
249  //    print_r('walk '.$key."<br/>\n");
250    }
251    while ($changed);
252  }
253
254  /*private*/ function _mergeAttributesAndContent(&$value)
255  {
256    if ( !is_array($value) )
257      return;
258/*    $first_key = '';
259    if (count($value)) { $ak = array_keys($value); $first_key = $ak[0]; }
260
261    print_r( '_mergeAttributesAndContent is_struct='.PwgResponseEncoder::is_struct($value)
262      .' count='.count($value)
263      .' first_key='.$first_key
264      ."<br/>\n"
265      );*/
266    $ret = 0;
267    if (PwgResponseEncoder::is_struct($value))
268    {
269      if ( isset($value[WS_XML_ATTRIBUTES]) )
270      {
271        $value = array_merge( $value, $value[WS_XML_ATTRIBUTES] );
272        unset( $value[WS_XML_ATTRIBUTES] );
273        $ret=1;
274      }
275      if ( isset($value[WS_XML_CONTENT]) )
276      {
277        $cont_processed = 0;
278        if ( count($value)==1 )
279        {
280          $value = $value[WS_XML_CONTENT];
281          $cont_processed=1;
282        }
283        else
284        {
285          if (PwgResponseEncoder::is_struct($value[WS_XML_CONTENT]))
286          {
287            $value = array_merge( $value, $value[WS_XML_CONTENT] );
288            unset( $value[WS_XML_CONTENT] );
289            $cont_processed=1;
290          }
291        }
292        $ret += $cont_processed;
293        if (!$cont_processed)
294        {
295          $value['_content'] = $value[WS_XML_CONTENT];
296          unset( $value[WS_XML_CONTENT] );
297          $ret++;
298        }
299      }
300    }
301
302    foreach ($value as $key=>$v)
303    {
304      if ( PwgResponseEncoder::_mergeAttributesAndContent($v) )
305      {
306        $value[$key]=$v;
307        $ret++;
308      }
309    }
310    return $ret;
311  }
312
313  /*private*/ function _removeNamedArray(&$value)
314  {
315    if ( strtolower( get_class($value) ) =='pwgnamedarray')
316    {
317      $value = $value->_content;
318      return 1;
319    }
320    return 0;
321  }
322
323  /*private*/ function _removeNamedStruct(&$value)
324  {
325    if ( strtolower( get_class($value) ) =='pwgnamedstruct')
326    {
327      if ( isset($value->_content['']) )
328      {
329        $unknown = $value->_content[''];
330        unset( $value->_content[''] );
331        $value->_content[$value->_name] = $unknown;
332      }
333      $value = $value->_content;
334      return 1;
335    }
336    return 0;
337  }
338}
339
340
341
342class PwgServer
343{
344  var $_requestHandler;
345  var $_requestFormat;
346  var $_responseEncoder;
347  var $_responseFormat;
348
349  var $_methods = array();
350
351  function PwgServer()
352  {
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(400);
382      @header("Content-Type: text/plain");
383      echo ("Cannot process your request. Unknown response format.
384Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseFormat."\n");
385      var_export($this);
386      die(0);
387    }
388
389    if ( is_null($this->_requestHandler) )
390    {
391      $this->sendResponse(
392        new PwgError(400, 'Unknown request format')
393        );
394      return;
395    }
396
397    $this->addMethod('reflection.getMethodList',
398        array('PwgServer', 'ws_getMethodList'),
399        null, '' );
400    $this->addMethod('reflection.getMethodDetails',
401        array('PwgServer', 'ws_getMethodDetails'),
402        array('methodName'),'');
403
404    trigger_action('ws_add_methods', array(&$this) );
405    uksort( $this->_methods, 'strnatcmp' );
406    $this->_requestHandler->handleRequest($this);
407  }
408
409  /**
410   * Encodes a response and sends it back to the browser.
411   */
412  function sendResponse($response)
413  {
414    $encodedResponse = $this->_responseEncoder->encodeResponse($response);
415    $contentType = $this->_responseEncoder->getContentType();
416
417    @header('Content-Type: '.$contentType);
418    print_r($encodedResponse);
419  }
420
421  /**
422   * Registers a web service method.
423   * @param methodName string - the name of the method as seen externally
424   * @param callback mixed - php method to be invoked internally
425   * @param params array - map of allowed parameter names with optional default
426   * values and parameter flags. Example of $params:
427   * array( 'param1' => array('default'=>523, 'flags'=>WS_PARAM_FORCE_ARRAY) ) .
428   * Possible parameter flags are:
429   * WS_PARAM_ALLOW_ARRAY - this parameter can be an array
430   * WS_PARAM_FORCE_ARRAY - if this parameter is scalar, force it to an array
431   *  before invoking the method
432   * @param description string - a description of the method.
433   */
434  function addMethod($methodName, $callback, $params=array(), $description, $include_file='')
435  {
436    if (!is_array($params))
437    {
438      $params = array();
439    }
440
441    if ( range(0, count($params) - 1) === array_keys($params) )
442    {
443      $params = array_flip($params);
444    }
445
446    foreach( $params as $param=>$options)
447    {
448      if ( !is_array($options) )
449      {
450        $params[$param] = array('flags'=>0);
451      }
452      else
453      {
454        $flags = isset($options['flags']) ? $options['flags'] : 0;
455        if ( array_key_exists('default', $options) )
456        {
457          $flags |= WS_PARAM_OPTIONAL;
458        }
459        if ( $flags & WS_PARAM_FORCE_ARRAY )
460        {
461          $flags |= WS_PARAM_ACCEPT_ARRAY;
462        }
463        $options['flags'] = $flags;
464        $params[$param] = $options;
465      }
466    }
467
468    $this->_methods[$methodName] = array(
469      'callback'    => $callback,
470      'description' => $description,
471      'signature'   => $params,
472      'include'     => $include_file,
473      );
474  }
475
476  function hasMethod($methodName)
477  {
478    return isset($this->_methods[$methodName]);
479  }
480
481  function getMethodDescription($methodName)
482  {
483    $desc = @$this->_methods[$methodName]['description'];
484    return isset($desc) ? $desc : '';
485  }
486
487  function getMethodSignature($methodName)
488  {
489    $signature = @$this->_methods[$methodName]['signature'];
490    return isset($signature) ? $signature : array();
491  }
492
493  /*static*/ function isPost()
494  {
495    return isset($HTTP_RAW_POST_DATA) or !empty($_POST);
496  }
497
498  /*static*/ function makeArrayParam(&$param)
499  {
500    if ( $param==null )
501    {
502      $param = array();
503    }
504    else
505    {
506      if (! is_array($param) )
507      {
508        $param = array($param);
509      }
510    }
511  }
512
513  /**
514   *  Invokes a registered method. Returns the return of the method (or
515   *  a PwgError object if the method is not found)
516   *  @param methodName string the name of the method to invoke
517   *  @param params array array of parameters to pass to the invoked method
518   */
519  function invoke($methodName, $params)
520  {
521    $method = @$this->_methods[$methodName];
522
523    if ( $method==null )
524    {
525      return new PwgError(WS_ERR_INVALID_METHOD, 'Method name "'.$methodName.'" is not valid');
526    }
527
528    // parameter check and data coercion !
529    $signature = $method['signature'];
530    $missing_params = array();
531    foreach($signature as $name=>$options)
532    {
533      $flags = $options['flags'];
534      if ( !array_key_exists($name, $params) )
535      {// parameter not provided in the request
536        if ( !($flags&WS_PARAM_OPTIONAL) )
537        {
538          $missing_params[] = $name;
539        }
540        else if ( array_key_exists('default',$options) )
541        {
542          $params[$name] = $options['default'];
543          if ( ($flags&WS_PARAM_FORCE_ARRAY) )
544          {
545            $this->makeArrayParam( $params[$name] );
546          }
547        }
548      }
549      else
550      {// parameter provided - do some basic checks
551        $the_param = $params[$name];
552        if ( is_array($the_param) and ($flags&WS_PARAM_ACCEPT_ARRAY)==0 )
553        {
554          return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be scalar' );
555        }
556        if ( ($flags&WS_PARAM_FORCE_ARRAY) )
557        {
558          $this->makeArrayParam( $the_param );
559        }
560        if ( isset($options['maxValue']) and $the_param>$options['maxValue'])
561        {
562          $the_param = $options['maxValue'];
563        }
564        $params[$name] = $the_param;
565      }
566    }
567    if (count($missing_params))
568    {
569      return new PwgError(WS_ERR_MISSING_PARAM, 'Missing parameters: '.implode(',',$missing_params));
570    }
571    $result = trigger_event('ws_invoke_allowed', true, $methodName, $params);
572    if ( strtolower( get_class($result) )!='pwgerror')
573    {
574      if ( !empty($method['include']) )
575      {
576        include_once( $method['include'] );
577      }
578      $result = call_user_func_array($method['callback'], array($params, &$this) );
579    }
580    return $result;
581  }
582
583  /**
584   * WS reflection method implementation: lists all available methods
585   */
586  /*static*/ function ws_getMethodList($params, &$service)
587  {
588    return array('methods' => new PwgNamedArray( array_keys($service->_methods),'method' ) );
589  }
590
591  /**
592   * WS reflection method implementation: gets information about a given method
593   */
594  /*static*/ function ws_getMethodDetails($params, &$service)
595  {
596    $methodName = $params['methodName'];
597    if (!$service->hasMethod($methodName))
598    {
599      return new PwgError(WS_ERR_INVALID_PARAM,
600            'Requested method does not exist');
601    }
602    $res = array(
603      'name' => $methodName,
604      'description' => $service->getMethodDescription($methodName),
605      'params' => array(),
606    );
607    $signature = $service->getMethodSignature($methodName);
608    foreach ($signature as $name => $options)
609    {
610      $param_data = array(
611        'name' => $name,
612        'optional' => ($options['flags']&WS_PARAM_OPTIONAL)?true:false,
613        'acceptArray' => ($options['flags']&WS_PARAM_ACCEPT_ARRAY)?true:false,
614        );
615      if (isset($options['default']))
616      {
617        $param_data['defaultValue'] = $options['default'];
618      }
619      $res['params'][] = $param_data;
620    }
621    return $res;
622  }
623}
624?>
Note: See TracBrowser for help on using the repository browser.