diff options
author | rvelices <rv-github@modusoptimus.com> | 2007-01-06 11:13:08 +0000 |
---|---|---|
committer | rvelices <rv-github@modusoptimus.com> | 2007-01-06 11:13:08 +0000 |
commit | f992150313a63d30aeda6e5f3dcd64fc62a06adb (patch) | |
tree | 6295bead230ea82610b53a09a9d52b15a6991768 | |
parent | 6314122c5d610f2fbc9fc5c60dec32f7f901a2bb (diff) |
Web service first version.
git-svn-id: http://piwigo.org/svn/trunk@1698 68402e56-0260-453c-a942-63ccdbb3a9ee
Diffstat (limited to '')
-rw-r--r-- | include/ws_core.inc.php | 611 | ||||
-rw-r--r-- | include/ws_functions.inc.php | 716 | ||||
-rw-r--r-- | include/ws_protocols/json_encoder.php | 90 | ||||
-rw-r--r-- | include/ws_protocols/php_encoder.php | 57 | ||||
-rw-r--r-- | include/ws_protocols/rest_encoder.php | 285 | ||||
-rw-r--r-- | include/ws_protocols/rest_handler.php | 60 | ||||
-rw-r--r-- | include/ws_protocols/xmlrpc_encoder.php | 118 | ||||
-rw-r--r-- | tools/prototype.js | 1781 | ||||
-rw-r--r-- | tools/ws.htm | 346 | ||||
-rw-r--r-- | ws.php | 183 |
10 files changed, 4247 insertions, 0 deletions
diff --git a/include/ws_core.inc.php b/include/ws_core.inc.php new file mode 100644 index 000000000..61c94b295 --- /dev/null +++ b/include/ws_core.inc.php @@ -0,0 +1,611 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +/**** WEB SERVICE CORE CLASSES************************************************ + * PwgServer - main object - the link between web service methods, request + * handler and response encoder + * PwgRequestHandler - base class for handlers + * PwgResponseEncoder - base class for response encoders + * PwgError, PwgNamedArray, PwgNamedStruct - can be used by web service functions + * as return values + */ + + +define( 'WS_PARAM_ACCEPT_ARRAY', 0x010000 ); +define( 'WS_PARAM_FORCE_ARRAY', 0x030000 ); +define( 'WS_PARAM_OPTIONAL', 0x040000 ); + +define( 'WS_ERR_INVALID_METHOD', 1001 ); +define( 'WS_ERR_MISSING_PARAM', 1002 ); +define( 'WS_ERR_INVALID_PARAM', 1003 ); + +define( 'WS_XML_ATTRIBUTES', 'attributes_xml_'); +define( 'WS_XML_CONTENT', 'content_xml_'); + +/** + * PwgError object can be returned from any web service function implementation. + */ +class PwgError +{ + var $_code; + var $_codeText; + + function PwgError($code, $codeText) + { + $this->_code = $code; + $this->_codeText = $codeText; + } + + function code() { return $this->_code; } + function message() { return $this->_codeText; } +} + +/** + * Simple wrapper around an array (keys are consecutive integers starting at 0). + * Provides naming clues for xml output (xml attributes vs. xml child elements?) + * Usually returned by web service function implementation. + */ +class PwgNamedArray +{ + /*private*/ var $_content; + /*private*/ var $_itemName; + /*private*/ var $_xmlAttributes; + + /** + * Constructs a named array + * @param arr array (keys must be consecutive integers starting at 0) + * @param itemName string xml element name for values of arr (e.g. image) + * @param xmlAttributes array of sub-item attributes that will be encoded as + * xml attributes instead of xml child elements + */ + function PwgNamedArray(&$arr, $itemName, $xmlAttributes=array() ) + { + $this->_content = $arr; + $this->_itemName = $itemName; + $this->_xmlAttributes = array_flip($xmlAttributes); + } +} +/** + * Simple wrapper around a "struct" (php array whose keys are not consecutive + * integers starting at 0). Provides naming clues for xml output (what is xml + * attributes and what is element) + */ +class PwgNamedStruct +{ + /*private*/ var $_content; + /*private*/ var $_name; + /*private*/ var $_xmlAttributes; + + /** + * Constructs a named struct (usually returned by web service function + * implementation) + * @param name string - containing xml element name + * @param content array - the actual content (php array) + * @param xmlAttributes array - name of the keys in $content that will be + * encoded as xml attributes (if null - automatically prefer xml attributes + * whenever possible) + */ + function PwgNamedStruct($name, $content, $xmlAttributes=null, $xmlElements=null ) + { + $this->_name = $name; + $this->_content = $content; + if ( isset($xmlAttributes) ) + { + $this->_xmlAttributes = array_flip($xmlAttributes); + } + else + { + $this->_xmlAttributes = array(); + foreach ($this->_content as $key=>$value) + { + if (!empty($key) and (is_scalar($value) or is_null($value)) ) + { + if ( empty($xmlElements) or !in_array($key,$xmlElements) ) + { + $this->_xmlAttributes[$key]=1; + } + } + } + } + } +} + + +/** + * Replace array_walk_recursive() + * + * @category PHP + * @package PHP_Compat + * @link http://php.net/function.array_walk_recursive + * @author Tom Buskens <ortega@php.net> + * @author Aidan Lister <aidan@php.net> + * @version $Revision: 1.7 $ + * @since PHP 5 + * @require PHP 4.0.6 (is_callable) + */ +if (!function_exists('array_walk_recursive')) { + function array_walk_recursive(&$input, $funcname) + { + if (!is_callable($funcname)) { + if (is_array($funcname)) { + $funcname = $funcname[0] . '::' . $funcname[1]; + } + user_error('array_walk_recursive() Not a valid callback ' . $user_func, + E_USER_WARNING); + return; + } + + if (!is_array($input)) { + user_error('array_walk_recursive() The argument should be an array', + E_USER_WARNING); + return; + } + + $args = func_get_args(); + + foreach ($input as $key => $item) { + if (is_array($item)) { + array_walk_recursive($item, $funcname, $args); + $input[$key] = $item; + } else { + $args[0] = &$item; + $args[1] = &$key; + call_user_func_array($funcname, $args); + $input[$key] = $item; + } + } + } +} + +/** + * Abstract base class for request handlers. + */ +class PwgRequestHandler +{ + /** Virtual abstract method. Decodes the request (GET or POST) handles the + * method invocation as well as response sending. + */ + function handleRequest(&$server) { assert(false); } +} + +/** + * + * Base class for web service response encoder. + */ +class PwgResponseEncoder +{ + /** encodes the web service response to the appropriate output format + * @param response mixed the unencoded result of a service method call + */ + function encodeResponse($response) { assert(false); } + + /** default "Content-Type" http header for this kind of response format + */ + function getContentType() { assert(false); } + + /** + * returns true if the parameter is a 'struct' (php array type whose keys are + * NOT consecutive integers starting with 0) + */ + function is_struct(&$data) + { + if (is_array($data) ) + { + if (range(0, count($data) - 1) !== array_keys($data) ) + { # string keys, unordered, non-incremental keys, .. - whatever, make object + return true; + } + } + return false; + } + + /** + * removes all XML formatting from $response (named array, named structs, etc) + * usually called by every response encoder, except rest xml. + */ + function flattenResponse(&$response) + { + PwgResponseEncoder::_mergeAttributesAndContent($response); + PwgResponseEncoder::_removeNamedArray($response); + PwgResponseEncoder::_removeNamedStruct($response); + if (is_array($response)) + { // need to call 2 times (first time might add new arrays) + array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') ); + array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') ); + } +//print_r($response); + PwgResponseEncoder::_mergeAttributesAndContent($response); + } + + /*private*/ function _remove_named_callback(&$value, $key) + { + do + { + $changed = 0; + $changed += PwgResponseEncoder::_removeNamedArray($value); + $changed += PwgResponseEncoder::_removeNamedStruct($value); + // print_r('walk '.$key."<br/>\n"); + } + while ($changed); + } + + /*private*/ function _mergeAttributesAndContent(&$value) + { + if ( !is_array($value) ) + return; +/* $first_key = ''; + if (count($value)) { $ak = array_keys($value); $first_key = $ak[0]; } + + print_r( '_mergeAttributesAndContent is_struct='.PwgResponseEncoder::is_struct($value) + .' count='.count($value) + .' first_key='.$first_key + ."<br/>\n" + );*/ + $ret = 0; + if (PwgResponseEncoder::is_struct($value)) + { + if ( isset($value[WS_XML_ATTRIBUTES]) ) + { + $value = array_merge( $value, $value[WS_XML_ATTRIBUTES] ); + unset( $value[WS_XML_ATTRIBUTES] ); + $ret=1; + } + if ( isset($value[WS_XML_CONTENT]) ) + { + $cont_processed = 0; + if ( count($value)==1 ) + { + $value = $value[WS_XML_CONTENT]; + $cont_processed=1; + } + else + { + if (PwgResponseEncoder::is_struct($value[WS_XML_CONTENT])) + { + $value = array_merge( $value, $value[WS_XML_CONTENT] ); + unset( $value[WS_XML_CONTENT] ); + $cont_processed=1; + } + } + $ret += $cont_processed; + if (!$cont_processed) + { + $value['_content'] = $value[WS_XML_CONTENT]; + unset( $value[WS_XML_CONTENT] ); + $ret++; + } + } + } + + foreach ($value as $key=>$v) + { + if ( PwgResponseEncoder::_mergeAttributesAndContent($v) ) + { + $value[$key]=$v; + $ret++; + } + } + return $ret; + } + + /*private*/ function _removeNamedArray(&$value) + { + if ( strtolower( get_class($value) ) =='pwgnamedarray') + { + $value = $value->_content; + return 1; + } + return 0; + } + + /*private*/ function _removeNamedStruct(&$value) + { + if ( strtolower( get_class($value) ) =='pwgnamedstruct') + { + if ( isset($value->_content['']) ) + { + $unknown = $value->_content['']; + unset( $value->_content[''] ); + $value->_content[$value->_name] = $unknown; + } + $value = $value->_content; + return 1; + } + return 0; + } +} + + + +class PwgServer +{ + var $_requestHandler; + var $_requestFormat; + var $_responseEncoder; + var $_responseFormat; + + var $_methods; + var $_methodSignatures; + + function PwgServer() + { + $methods = array(); + } + + /** + * Initializes the request handler. + */ + function setHandler($requestFormat, &$requestHandler) + { + $this->_requestHandler = &$requestHandler; + $this->_requestFormat = $requestFormat; + } + + /** + * Initializes the request handler. + */ + function setEncoder($responseFormat, &$encoder) + { + $this->_responseEncoder = &$encoder; + $this->_responseFormat = $responseFormat; + } + + /** + * Runs the web service call (handler and response encoder should have been + * created) + */ + function run() + { + if ( is_null($this->_responseEncoder) ) + { + @header("HTTP/1.1 500 Server error"); + @header("Status: 500 Server error"); + @header("Content-Type: text/plain"); + echo ("Cannot process your request. Unknown response format. +Request format: ".@$this->_requestFormat." handler:".$this->_requestHandler." +Response format: ".@$this->_responseFormat." encoder:".$this->_responseEncoder." + "); + var_export($this); + die(0); + } + + if ( is_null($this->_requestHandler) ) + { + $this->sendResponse( + new PwgError(500, 'Unknown request format') + ); + return; + } + + $this->addMethod('reflection.getMethodList', + array('PwgServer', 'ws_getMethodList'), + null, '' ); + $this->addMethod('reflection.getMethodDetails', + array('PwgServer', 'ws_getMethodDetails'), + array('methodName'),''); + + trigger_action('ws_add_methods', array(&$this) ); + uksort( $this->_methods, 'strnatcmp' ); + $this->_requestHandler->handleRequest($this); + } + + /** + * Encodes a response and sends it back to the browser. + */ + function sendResponse($response) + { + $encodedResponse = $this->_responseEncoder->encodeResponse($response); + $contentType = $this->_responseEncoder->getContentType(); + + @header('Content-Type: '.$contentType); + print_r($encodedResponse); + } + + /** + * Registers a web service method. + * @param methodName string - the name of the method as seen externally + * @param callback mixed - php method to be invoked internally + * @param params array - map of allowed parameter names with optional default + * values and parameter flags. Example of $params: + * array( 'param1' => array('default'=>523, 'flags'=>WS_PARAM_FORCE_ARRAY) ) . + * Possible parameter flags are: + * WS_PARAM_ALLOW_ARRAY - this parameter can be an array + * WS_PARAM_FORCE_ARRAY - if this parameter is scalar, force it to an array + * before invoking the method + * @param description string - a description of the method. + */ + function addMethod($methodName, $callback, $params=array(), $description, $include_file='') + { + $this->_methods[$methodName] = $callback; + $this->_methodDescriptions[$methodName] = $description; + + if (!is_array($params)) + { + $params = array(); + } + + if ( range(0, count($params) - 1) === array_keys($params) ) + { + $params = array_flip($params); + } + + foreach( $params as $param=>$options) + { + if ( !is_array($options) ) + { + $params[$param] = array('flags'=>0); + } + else + { + $flags = isset($options['flags']) ? $options['flags'] : 0; + if ( array_key_exists('default', $options) ) + { + $flags |= WS_PARAM_OPTIONAL; + } + $options['flags'] = $flags; + $params[$param] = $options; + } + } + $this->_methodSignatures[$methodName] = $params; + } + + function hasMethod($methodName) + { + return isset($this->_methods[$methodName]); + } + + function getMethodDescription($methodName) + { + $desc = @$this->_methodDescriptions[$methodName]; + return isset($desc) ? $desc : ''; + } + + function getMethodSignature($methodName) + { + $signature = @$this->_methodSignatures[$methodName]; + return isset($signature) ? $signature : array(); + } + + /*static*/ function isPost() + { + return isset($HTTP_RAW_POST_DATA) or !empty($_POST); + } + + /*static*/ function makeArrayParam(&$param) + { + if ( $param==null ) + { + $param = array(); + } + else + { + if (! is_array($param) ) + { + $param = array($param); + } + } + } + + /** + * Invokes a registered method. Returns the return of the method (or + * a PwgError object if the method is not found) + * @param methodName string the name of the method to invoke + * @param params array array of parameters to pass to the invoked method + */ + function invoke($methodName, $params) + { + $callback = @$this->_methods[$methodName]; + + if ( $callback==null ) + { + return new PwgError(WS_ERR_INVALID_METHOD, 'Method name "'.$methodName.'" is not valid'); + } + + // parameter check and data coercion ! + $signature = @$this->_methodSignatures[$methodName]; + $missing_params = array(); + foreach($signature as $name=>$options) + { + $flags = $options['flags']; + if ( !array_key_exists($name, $params) ) + {// parameter not provided in the request + if ( !($flags&WS_PARAM_OPTIONAL) ) + { + $missing_params[] = $name; + } + else if ( array_key_exists('default',$options) ) + { + $params[$name] = $options['default']; + if ( ($flags&WS_PARAM_FORCE_ARRAY) ) + { + $this->makeArrayParam( $params[$name] ); + } + } + } + else + {// parameter provided - do some basic checks + $the_param = $params[$name]; + if ( is_array($the_param) and ($flags&WS_PARAM_ACCEPT_ARRAY)==0 ) + { + return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be scalar' ); + } + if ( ($flags&WS_PARAM_FORCE_ARRAY) ) + { + $this->makeArrayParam( $the_param ); + } + $params[$name] = $the_param; + } + } + if (count($missing_params)) + { + return new PwgError(WS_ERR_MISSING_PARAM, 'Missing parameters: '.implode(',',$missing_params)); + } + + $result = call_user_func_array($callback, array($params, &$this) ); + return $result; + } + + /** + * WS reflection method implementation: lists all available methods + */ + /*static*/ function ws_getMethodList($params, &$service) + { + return array('methods' => new PwgNamedArray( array_keys($service->_methods),'method' ) ); + } + + /** + * WS reflection method implementation: gets information about a given method + */ + /*static*/ function ws_getMethodDetails($params, &$service) + { + $methodName = $params['methodName']; + if (!$service->hasMethod($methodName)) + { + return new PwgError(WS_ERR_INVALID_PARAM, + 'Requested method does not exist'); + } + $res = array( + 'name' => $methodName, + 'description' => $service->getMethodDescription($methodName), + 'params' => array(), + ); + $signature = $service->getMethodSignature($methodName); + foreach ($signature as $name => $options) + { + $param_data = array( + 'name' => $name, + 'optional' => ($options['flags']&WS_PARAM_OPTIONAL)?true:false, + ); + if (isset($options['default'])) + { + $param_data['defaultValue'] = $options['default']; + } + $res['params'][] = $param_data; + } + return $res; + } +} +?> diff --git a/include/ws_functions.inc.php b/include/ws_functions.inc.php new file mode 100644 index 000000000..7e1f1a1ba --- /dev/null +++ b/include/ws_functions.inc.php @@ -0,0 +1,716 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +/**** IMPLEMENTATION OF WEB SERVICE METHODS ***********************************/ + +/** + * returns a "standard" (for our web service) array of sql where clauses that + * filters the images (images table only) + */ +function ws_std_image_sql_filter( $params, $tbl_name='' ) +{ + $clauses = array(); + if ( is_numeric($params['f_min_rate']) ) + { + $clauses[] = $tbl_name.'average_rate>'.$params['f_min_rate']; + } + if ( is_numeric($params['f_max_rate']) ) + { + $clauses[] = $tbl_name.'average_rate<='.$params['f_max_rate']; + } + if ( is_numeric($params['f_min_hit']) ) + { + $clauses[] = $tbl_name.'hit>'.$params['f_min_hit']; + } + if ( is_numeric($params['f_max_hit']) ) + { + $clauses[] = $tbl_name.'hit<='.$params['f_max_hit']; + } + if ( isset($params['f_min_date_posted']) ) + { + $clauses[] = $tbl_name."date_available>='".$params['f_min_date_posted']."'"; + } + if ( isset($params['f_max_date_posted']) ) + { + $clauses[] = $tbl_name."date_available<'".$params['f_max_date_posted']."'"; + } + if ( isset($params['f_min_date_created']) ) + { + $clauses[] = $tbl_name."date_creation>='".$params['f_min_date_created']."'"; + } + if ( isset($params['f_max_date_created']) ) + { + $clauses[] = $tbl_name."date_creation<'".$params['f_max_date_created']."'"; + } + if ( is_numeric($params['f_min_ratio']) ) + { + $clauses[] = $tbl_name.'width/'.$tbl_name.'height>'.$params['f_min_ratio']; + } + if ( is_numeric($params['f_max_ratio']) ) + { + $clauses[] = $tbl_name.'width/'.$tbl_name.'height<='.$params['f_max_ratio']; + } + if ( $params['f_with_thumbnail'] ) + { + $clauses[] = $tbl_name.'tn_ext IS NOT NULL'; + } + return $clauses; +} + +/** + * returns a "standard" (for our web service) ORDER BY sql clause for images + */ +function ws_std_image_sql_order( $params, $tbl_name='' ) +{ + $ret = ''; + if ( empty($params['order']) ) + { + return $ret; + } + $matches = array(); + preg_match_all('/([a-z_]+) *(?:(asc|desc)(?:ending)?)? *(?:, *|$)/i', + $params['order'], $matches); + for ($i=0; $i<count($matches[1]); $i++) + { + switch ($matches[1][$i]) + { + case 'date_created': + $matches[1][$i] = 'date_creation'; break; + case 'date_posted': + $matches[1][$i] = 'date_available'; break; + case 'rand': case 'random': + $matches[1][$i] = 'RAND()'; break; + } + $sortable_fields = array('id', 'file', 'name', 'hit', 'average_rate', + 'date_creation', 'date_available', 'RAND()' ); + if ( in_array($matches[1][$i], $sortable_fields) ) + { + if (!empty($ret)) + $ret .= ', '; + if ($matches[1][$i] != 'RAND()' ) + { + $ret .= $tbl_name; + } + $ret .= $matches[1][$i]; + $ret .= ' '.$matches[2][$i]; + } + } + return $ret; +} + +/** + * returns an array map of urls (thumb/element) for image_row - to be returned + * in a standard way by different web service methods + */ +function ws_std_get_urls($image_row) +{ + $ret = array( + 'tn_url' => get_thumbnail_url($image_row), + 'element_url' => get_element_url($image_row) + ); + global $user; + if ($user['enabled_high'] and $image_row['has_high'] ) + { + $ret['high_url'] = get_high_url($image_row); + } + return $ret; +} + + +function ws_getVersion($params, &$service) +{ + return PHPWG_VERSION; +} + +/** + * returns images per category (wb service method) + */ +function ws_categories_getImages($params, &$service) +{ + @include_once(PHPWG_ROOT_PATH.'include/functions_picture.inc.php'); + global $user, $conf; + + $images = array(); + + //------------------------------------------------- get the related categories + $where_clauses = array(); + foreach($params['cat_id'] as $cat_id) + { + $cat_id = (int)$cat_id; + if ($cat_id<=0) + continue; + if ($params['recursive']) + { + $where_clauses[] = 'uppercats REGEXP \'(^|,)'.$cat_id.'(,|$)\''; + } + else + { + $where_clauses[] = 'id='.$cat_id; + } + } + if (!empty($where_clauses)) + { + $where_clauses = array( '('. + implode(' + OR ', $where_clauses) . ')' + ); + } + $where_clauses[] = 'id NOT IN ('.$user['forbidden_categories'].')'; + + $query = ' +SELECT id, name, image_order + FROM '.CATEGORIES_TABLE.' + WHERE '. implode(' + AND ', $where_clauses); + $result = pwg_query($query); + $cats = array(); + while ($row = mysql_fetch_assoc($result)) + { + $row['id'] = (int)$row['id']; + $cats[ $row['id'] ] = $row; + } + + //-------------------------------------------------------- get the images + if ( !empty($cats) ) + { + $where_clauses = ws_std_image_sql_filter( $params, 'i.' ); + $where_clauses[] = 'category_id IN (' + .implode(',', array_keys($cats) ) + .')'; + $order_by = ws_std_image_sql_order($params, 'i.'); + if (empty($order_by)) + {// TODO check for category order by (image_order) + $order_by = $conf['order_by']; + } + else + { + $order_by = 'ORDER BY '.$order_by; + } + $query = ' +SELECT i.*, GROUP_CONCAT(category_id) cat_ids + FROM '.IMAGES_TABLE.' i + INNER JOIN '.IMAGE_CATEGORY_TABLE.' ON i.id=image_id + WHERE '. implode(' + AND ', $where_clauses).' +GROUP BY i.id +'.$order_by.' +LIMIT '.$params['per_page']*$params['page'].','.$params['per_page']; + + $result = pwg_query($query); + while ($row = mysql_fetch_assoc($result)) + { + $image = array(); + foreach ( array('id', 'width', 'height', 'hit') as $k ) + { + if (isset($row[$k])) + { + $image[$k] = (int)$row[$k]; + } + } + foreach ( array('name', 'file') as $k ) + { + $image[$k] = $row[$k]; + } + $image = array_merge( $image, ws_std_get_urls($row) ); + + $image_cats = array(); + foreach ( explode(',', $row['cat_ids']) as $cat_id ) + { + $url = make_index_url( + array( + 'category' => $cat_id, + 'cat_name' => $cats[$cat_id]['name'], + ) + ); + $page_url = make_picture_url( + array( + 'category' => $cat_id, + 'cat_name' => $cats[$cat_id]['name'], + 'image_id' => $row['id'], + 'image_file' => $row['file'], + ) + ); + array_push( $image_cats, array( + WS_XML_ATTRIBUTES => array ( + 'id' => (int)$cat_id, + 'url' => $url, + 'page_url' => $page_url, + ) + ) + ); + } + + $image['categories'] = new PwgNamedArray( + $image_cats,'category', array('id','url','page_url') + ); + array_push($images, $image); + } + } + + return array( 'images' => + array ( + WS_XML_ATTRIBUTES => + array( + 'page' => $params['page'], + 'per_page' => $params['per_page'], + 'count' => count($images) + ), + WS_XML_CONTENT => new PwgNamedArray($images, 'image', + array('id', 'tn_url', 'element_url', 'file','width','height','hit') ) + ) + ); +} + +/** + * returns a list of categories + */ +function ws_categories_getList($params, &$service) +{ + global $user; + + $query = ' +SELECT id, name, uppercats, global_rank, + max_date_last, count_images AS nb_images, count_categories AS nb_categories + FROM '.CATEGORIES_TABLE.' + INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.' ON id=cat_id'; + + $where = array(); + $where[]= 'user_id='.$user['id']; + if ($params['cat_id']>0) + { + $where[] = 'uppercats REGEXP \'(^|,)'. + (int)($params['cat_id']) + .'(,|$)\''; + } + + if (!$params['recursive']) + { + if ($params['cat_id']>0) + $where[] = 'id_uppercat='.(int)($params['cat_id']); + else + $where[] = 'id_uppercat IS NULL'; + } + + if ($params['public']) + { + $where[] = 'status = "public"'; + } + else + { + $where[] = 'id NOT IN ('.$user['forbidden_categories'].')'; + } + + $query .= ' + WHERE '. implode(' + AND ', $where); + $query .= ' +ORDER BY global_rank'; + + $result = pwg_query($query); + + $cats = array(); + while ($row = mysql_fetch_assoc($result)) + { + $row['url'] = make_index_url( + array( + 'category' => $row['id'], + 'cat_name' => $row['name'], + ) + ); + foreach( array('id','nb_images','nb_categories') as $key) + { + $row[$key] = (int)$row[$key]; + } + array_push($cats, $row); + } + usort($cats, 'global_rank_compare'); + return array( + 'categories' => + new PwgNamedArray($cats,'category', + array('id','url','nb_images','nb_categories','max_date_last') + ) + ); +} + +function ws_images_getInfo($params, &$service) +{ + @include_once(PHPWG_ROOT_PATH.'include/functions_picture.inc.php'); + global $user; + $params['image_id'] = (int)$params['image_id']; + if ( $params['image_id']<=0 ) + { + return new PwgError(WS_ERR_INVALID_PARAM, "Invalid image_id"); + } + $query=' +SELECT * FROM '.IMAGES_TABLE.' + WHERE id='.$params['image_id'].' +LIMIT 1'; + $image_row = mysql_fetch_assoc(pwg_query($query)); + if ($image_row==null) + { + return new PwgError(999, "image_id not found"); + } + array_merge( $image_row, ws_std_get_urls($image_row) ); + + //-------------------------------------------------------- related categories + $query = ' +SELECT c.id,c.name,c.uppercats,c.global_rank + FROM '.IMAGE_CATEGORY_TABLE.' + INNER JOIN '.CATEGORIES_TABLE.' c ON category_id = id + WHERE image_id = '.$image_row['id'].' + AND category_id NOT IN ('.$user['forbidden_categories'].') +;'; + $result = pwg_query($query); + $related_categories = array(); + while ($row = mysql_fetch_assoc($result)) + { + $row['url'] = make_index_url( + array( + 'category' => $row['id'], + 'cat_name' => $row['name'], + ) + ); + + $row['page_url'] = make_picture_url( + array( + 'image_id' => $image_row['id'], + 'image_file' => $image_row['file'], + 'category' => $row['id'], + 'cat_name' => $row['name'], + ) + ); + array_push($related_categories, $row); + } + usort($related_categories, 'global_rank_compare'); + if ( empty($related_categories) ) + { + return new PwgError(401, 'Access denied'); + } + + //-------------------------------------------------------------- related tags + $query = ' +SELECT id, name, url_name + FROM '.IMAGE_TAG_TABLE.' + INNER JOIN '.TAGS_TABLE.' ON tag_id = id + WHERE image_id = '.$image_row['id'].' +;'; + $result = pwg_query($query); + $related_tags = array(); + while ($row = mysql_fetch_assoc($result)) + { + $row['url'] = make_index_url( + array( + 'tags' => array($row) + ) + ); + $row['page_url'] = make_picture_url( + array( + 'image_id' => $image_row['id'], + 'image_file' => $image_row['file'], + 'tags' => array($row), + ) + ); + array_push($related_tags, $row); + } + //---------------------------------------------------------- related comments + $query = ' +SELECT COUNT(id) nb_comments + FROM '.COMMENTS_TABLE.' + WHERE image_id = '.$image_row['id']; + list($nb_comments) = array_from_query($query, 'nb_comments'); + + $query = ' +SELECT id, date, author, content + FROM '.COMMENTS_TABLE.' + WHERE image_id = '.$image_row['id'].' + AND validated="true"'; + $query .= ' + ORDER BY date DESC + LIMIT 0, 5'; + + $result = pwg_query($query); + $related_comments = array(); + while ($row = mysql_fetch_assoc($result)) + { + array_push($related_comments, $row); + } + + //------------------------------------------------------------- related rates + $query = ' +SELECT COUNT(rate) AS count + , ROUND(AVG(rate),2) AS average + , ROUND(STD(rate),2) AS stdev + FROM '.RATE_TABLE.' + WHERE element_id = '.$image_row['id'].' +;'; + $row = mysql_fetch_assoc(pwg_query($query)); + + $ret = $image_row; + $ret['rates'] = array( WS_XML_ATTRIBUTES => $row ); + $ret['categories'] = new PwgNamedArray($related_categories, 'category', array('id','url', 'page_url') ); + $ret['tags'] = new PwgNamedArray($related_tags, 'tag', array('id','url_name','url','page_url') ); + $ret['comments'] = array( + WS_XML_ATTRIBUTES => array('nb_comments' => $nb_comments), + WS_XML_CONTENT => new PwgNamedArray($related_comments, 'comment', array('id') ) + ); + unset($ret['path']); + unset($ret['storage_category_id']); + return new PwgNamedStruct('image',$ret, null, array('name','comment') ); +} + + +function ws_session_login($params, &$service) +{ + global $conf; + + if (!$service->isPost()) + { + return new PwgError(400, "This method requires POST"); + } + + $username = $params['username']; + // retrieving the encrypted password of the login submitted + $query = ' +SELECT '.$conf['user_fields']['id'].' AS id, + '.$conf['user_fields']['password'].' AS password + FROM '.USERS_TABLE.' + WHERE '.$conf['user_fields']['username'].' = \''.$username.'\' +;'; + $row = mysql_fetch_assoc(pwg_query($query)); + + if ($row['password'] == $conf['pass_convert']($params['password'])) + { + log_user($row['id'], false); + return true; + } + return new PwgError(999, 'Invalid username/password'); +} + +function ws_session_logout($params, &$service) +{ + global $user, $conf; + if (!$user['is_the_guest']) + { + $_SESSION = array(); + session_unset(); + session_destroy(); + setcookie(session_name(),'',0, + ini_get('session.cookie_path'), + ini_get('session.cookie_domain') + ); + setcookie($conf['remember_me_name'], '', 0, cookie_path()); + } + return true; +} + +function ws_session_getStatus($params, &$service) +{ + global $user; + $res = array(); + $res['username'] = $user['is_the_guest'] ? 'guest' : $user['username']; + $res['status'] = $user['status']; + return $res; +} + + +function ws_tags_getList($params, &$service) +{ + global $user; + $tags = get_available_tags(explode(',', $user['forbidden_categories'])); + if ($params['sort_by_counter']) + { + usort($tags, create_function('$a,$b', 'return -$a["counter"]+$b["counter"];') ); + } + else + { + usort($tags, 'name_compare'); + } + for ($i=0; $i<count($tags); $i++) + { + $tags[$i]['id'] = (int)$tags[$i]['tag_id']; + $tags[$i]['counter'] = (int)$tags[$i]['counter']; + unset($tags[$i]['tag_id']); + $tags[$i]['url'] = make_index_url( + array( + 'section'=>'tags', + 'tags'=>array($tags[$i]) + ) + ); + } + return array('tags' => new PwgNamedArray($tags, 'tag', array('id','url_name','url', 'counter' )) ); +} + +function ws_tags_getImages($params, &$service) +{ + @include_once(PHPWG_ROOT_PATH.'include/functions_picture.inc.php'); + global $user, $conf; + + // first build all the tag_ids we are interested in + $tag_ids = array(); + $tags = get_available_tags(); + $tags_by_id = array(); + for( $i=0; $i<count($tags); $i++ ) + { + $tags[$i]['tag_id']=(int)$tags[$i]['tag_id']; + $tags[$i]['id']=(int)$tags[$i]['tag_id']; //required by make_xxx_url + } + foreach( $tags as $tag ) + { + $tags_by_id[ $tag['tag_id'] ] = $tag; + if ( + in_array($tag['name'], $params['tag_name']) + or + in_array($tag['url_name'], $params['tag_url_name']) + ) + { + $tag_ids[] = $tag['tag_id']; + } + } + unset($tags); + + foreach( $params['tag_id'] as $tag_id ) + { + if ( (int)$tag_id > 0 ) + { + $tag_ids[] = $tag_id; + } + } + + $tag_ids = array_unique( $tag_ids ); + + $image_ids = array(); + $image_tag_map = array(); + + if ( !empty($tag_ids) ) + { // build list of image ids with associated tags per image + if ($params['tag_mode_and']) + { + $image_ids = get_image_ids_for_tags( $tag_ids ); + } + else + { + $query = ' +SELECT image_id, GROUP_CONCAT(tag_id) tag_ids + FROM '.IMAGE_TAG_TABLE.' + WHERE tag_id IN ('.implode(',',$tag_ids).') + GROUP BY image_id'; + $result = pwg_query($query); + while ( $row=mysql_fetch_assoc($result) ) + { + $row['image_id'] = (int)$row['image_id']; + array_push( $image_ids, $row['image_id'] ); + $image_tag_map[ $row['image_id'] ] = explode(',', $row['tag_ids']); + } + } + } + + $images = array(); + if ( !empty($image_ids)) + { + $where_clauses = ws_std_image_sql_filter($params); + $where_clauses[] = 'category_id NOT IN ('.$user['forbidden_categories'].')'; + $where_clauses[] = 'id IN ('.implode(',',$image_ids).')'; + $order_by = ws_std_image_sql_order($params); + if (empty($order_by)) + { + $order_by = $conf['order_by']; + } + else + { + $order_by = 'ORDER BY '.$order_by; + } + + $query = ' +SELECT DISTINCT i.* FROM '.IMAGES_TABLE.' i + INNER JOIN '.IMAGE_CATEGORY_TABLE.' ON i.id=image_id + WHERE '. implode(' + AND ', $where_clauses).' +'.$order_by.' +LIMIT '.$params['per_page']*$params['page'].','.$params['per_page']; + + $result = pwg_query($query); + while ($row = mysql_fetch_assoc($result)) + { + foreach ( array('id', 'width', 'height', 'hit') as $k ) + { + if (isset($row[$k])) + { + $image[$k] = (int)$row[$k]; + } + } + foreach ( array('name', 'file') as $k ) + { + $image[$k] = $row[$k]; + } + $image = array_merge( $image, ws_std_get_urls($row) ); + + $image_tag_ids = ($params['tag_mode_and']) ? $tag_ids : $image_tag_map[$image['id']]; + $image_tags = array(); + foreach ($image_tag_ids as $tag_id) + { + $url = make_index_url( + array( + 'section'=>'tags', + 'tags'=> array($tags_by_id[$tag_id]) + ) + ); + $page_url = make_picture_url( + array( + 'section'=>'tags', + 'tags'=> array($tags_by_id[$tag_id]), + 'image_id' => $row['id'], + 'image_file' => $row['file'], + ) + ); + array_push($image_tags, array( + 'id' => (int)$tag_id, + 'url' => $url, + 'page_url' => $page_url, + ) + ); + } + $image['tags'] = new PwgNamedArray($image_tags, 'tag', + array('id','url_name','url','page_url') + ); + array_push($images, $image); + } + } + + return array( 'images' => + array ( + WS_XML_ATTRIBUTES => + array( + 'page' => $params['page'], + 'per_page' => $params['per_page'], + 'count' => count($images) + ), + WS_XML_CONTENT => new PwgNamedArray($images, 'image', + array('id', 'tn_url', 'element_url', 'file','width','height','hit') ) + ) + ); +} + +?> diff --git a/include/ws_protocols/json_encoder.php b/include/ws_protocols/json_encoder.php new file mode 100644 index 000000000..ffeb59954 --- /dev/null +++ b/include/ws_protocols/json_encoder.php @@ -0,0 +1,90 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + + +#_____________________ PHP 5.2 +if (! function_exists('json_encode')) { + function json_encode($data) { + switch (gettype($data)) { + case 'boolean': + return ($data ? 'true' : 'false'); + case 'null': + case 'NULL': + return 'null'; + case 'integer': + case 'double': + return $data; + case 'string': + return '"'. str_replace(array("\\",'"',"/","\n","\r","\t"), array("\\\\",'\"',"\\/","\\n","\\r","\\t"), $data) .'"'; + case 'object': + case 'array': + if ($data === array()) return '[]'; # empty array + if (range(0, count($data) - 1) !== array_keys($data) ) { # string keys, unordered, non-incremental keys, .. - whatever, make object + $out = "\n".'{'; + foreach($data as $key => $value) { + $out .= json_encode((string) $key) . ':' . json_encode($value) . ','; + } + $out = substr($out, 0, -1) . "\n". '}'; + }else{ + # regular array + $out = "\n".'[' . join("\n".',', array_map('json_encode', $data)) ."\n".']'; + } + return $out; + } + } +} + +class PwgJsonEncoder extends PwgResponseEncoder +{ + function encodeResponse($response) + { + $respClass = strtolower( get_class($response) ); + if ($respClass=='pwgerror') + { + return json_encode( + array( + 'stat' => 'fail', + 'err' => $response->code(), + 'message' => $response->message(), + ) + ); + } + parent::flattenResponse($response); + return json_encode( + array( + 'stat' => 'ok', + 'result' => $response, + ) + ); + } + + function getContentType() + { + return 'text/plain'; + } +} + +?> diff --git a/include/ws_protocols/php_encoder.php b/include/ws_protocols/php_encoder.php new file mode 100644 index 000000000..f12df2594 --- /dev/null +++ b/include/ws_protocols/php_encoder.php @@ -0,0 +1,57 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +class PwgSerialPhpEncoder extends PwgResponseEncoder +{ + function encodeResponse($response) + { + $respClass = strtolower( get_class($response) ); + if ($respClass=='pwgerror') + { + return serialize( + array( + 'stat' => 'fail', + 'err' => $response->code(), + 'message' => $response->message(), + ) + ); + } + parent::flattenResponse($response); + return serialize( + array( + 'stat' => 'ok', + 'result' => $response + ) + ); + } + + function getContentType() + { + return 'text/plain'; + } +} + +?> diff --git a/include/ws_protocols/rest_encoder.php b/include/ws_protocols/rest_encoder.php new file mode 100644 index 000000000..c66cf2536 --- /dev/null +++ b/include/ws_protocols/rest_encoder.php @@ -0,0 +1,285 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + + +class PwgXmlWriter +{ + var $_indent; + var $_indentStr; + + var $_elementStack; + var $_lastTagOpen; + var $_indentLevel; + + var $_encodedXml; + + function PwgXmlWriter() + { + $this->_elementStack = array(); + $this->_lastTagOpen = false; + $this->_indentLevel = 0; + + $this->_encodedXml = ''; + $this->_indent = true; + $this->_indentStr = "\t"; + } + + function &getOutput() + { + return $this->_encodedXml; + } + + + function start_element($name) + { + $this->_end_prev(false); + if (!empty($this->_elementStack)) + { + $this->_eol_indent(); + } + $this->_indentLevel++; + $this->_indent(); + $this->_output( '<'.$name ); + $this->_lastTagOpen = true; + array_push( $this->_elementStack, $name); + } + + function end_element($x) + { + $close_tag = $this->_end_prev(true); + $name = array_pop( $this->_elementStack ); + if ($close_tag) + { + $this->_indentLevel--; + $this->_indent(); +// $this->_eol_indent(); + $this->_output('</'.$name.">"); + } + } + + function write_content($value) + { + $this->_end_prev(false); + $value = (string)$value; + $need_cdata = (strpos($value, "\r")!==false)?true:false; + if ($need_cdata) + { + $this->_output( '<![CDATA[' . $value . ']]>' ); + } + else + { + $this->_output( htmlspecialchars( $value ) ); + } + } + + function write_attribute($name, $value) + { + $this->_output(' '.$name.'="'.$this->encode_attribute($value).'"'); + } + + function encode_attribute($value) + { + return htmlspecialchars( (string)$value); + } + + function _end_prev($done) + { + $ret = true; + if ($this->_lastTagOpen) + { + if ($done) + { + $this->_indentLevel--; + $this->_output( ' />' ); + //$this->_eol_indent(); + $ret = false; + } + else + { + $this->_output( '>' ); + } + $this->_lastTagOpen = false; + } + return $ret; + } + + function _eol_indent() + { + if ($this->_indent) + $this->_output("\n"); + } + + function _indent() + { + if ($this->_indent and + $this->_indentLevel > count($this->_elementStack) ) + { + $this->_output( + str_repeat( $this->_indentStr, count($this->_elementStack) ) + ); + } + } + + function _output($raw_content) + { + $this->_encodedXml .= $raw_content; + } +} + +class PwgRestEncoder extends PwgResponseEncoder +{ + function encodeResponse($response) + { + global $lang_info; + $respClass = strtolower( get_class($response) ); + if ($respClass=='pwgerror') + { + $ret = '<?xml version="1.0"?> +<rsp stat="fail"> + <err code="'.$response->code().'" msg="'.htmlspecialchars($response->message()).'" /> +</rsp>'; + return $ret; + } + +//parent::flattenResponse($response); + + $this->_writer = new PwgXmlWriter(); + $this->encode($response); + $ret = $this->_writer->getOutput(); + $ret = '<?xml version="1.0" encoding="'.$lang_info['charset'].'" ?> +<rsp stat="ok"> +'.$ret.' +</rsp>'; + + return $ret; + } + + function getContentType() + { + return 'text/xml'; + } + + function encode_array($data, $itemName, $xml_attributes=array()) + { + foreach ($data as $item) + { + $this->_writer->start_element( $itemName ); + $this->encode($item, $xml_attributes); + $this->_writer->end_element( $itemName ); + } + } + + function encode_struct($data, $skip_underscore, $xml_attributes=array()) + { + foreach ($data as $name => $value) + { + if (is_numeric($name)) + continue; + if ($skip_underscore and $name[0]=='_') + continue; + if ( is_null($value) ) + continue; // null means we dont put it + if ( $name==WS_XML_ATTRIBUTES) + { + foreach ($value as $attr_name => $attr_value) + { + $this->_writer->write_attribute($attr_name, $attr_value); + } + unset($data[$name]); + } + else if ( isset($xml_attributes[$name]) ) + { + $this->_writer->write_attribute($name, $value); + unset($data[$name]); + } + } + + foreach ($data as $name => $value) + { + if (is_numeric($name)) + continue; + if ($skip_underscore and $name[0]=='_') + continue; + if ( is_null($value) ) + continue; // null means we dont put it + if ($name!=WS_XML_CONTENT) + $this->_writer->start_element($name); + $this->encode($value); + if ($name!=WS_XML_CONTENT) + $this->_writer->end_element($name); + } + } + + function encode($data, $xml_attributes=array() ) + { + switch (gettype($data)) + { + case 'null': + case 'NULL': + $this->_writer->write_content(''); + break; + case 'boolean': + $this->_writer->write_content($data ? '1' : '0'); + break; + case 'integer': + case 'double': + $this->_writer->write_content($data); + break; + case 'string': + $this->_writer->write_content($data); + break; + case 'array': + $is_array = range(0, count($data) - 1) === array_keys($data); + if ($is_array) + { + $this->encode_array($data, 'item' ); + } + else + { + $this->encode_struct($data, false, $xml_attributes); + } + break; + case 'object': + switch ( strtolower(get_class($data)) ) + { + case 'pwgnamedarray': + $this->encode_array($data->_content, $data->_itemName, $data->_xmlAttributes); + break; + case 'pwgnamedstruct': + $this->encode_array( array($data->_content), $data->_name, $data->_xmlAttributes); + break; + default: + $this->encode_struct(get_object_vars($data), true); + break; + } + break; + default: + trigger_error("Invalid type ". gettype($data)." ".get_class($data), E_USER_WARNING ); + } + } +} + +?> diff --git a/include/ws_protocols/rest_handler.php b/include/ws_protocols/rest_handler.php new file mode 100644 index 000000000..184fc205a --- /dev/null +++ b/include/ws_protocols/rest_handler.php @@ -0,0 +1,60 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +class PwgRestRequestHandler +{ + function handleRequest(&$service) + { + $params = array(); + + $param_array = $service->isPost() ? $_POST : $_GET; + foreach ($param_array as $name => $value) + { + if ($name=='format') + continue; + if ($name=='method') + { + $method = $value; + } + else + { + $params[$name]=$value; + } + } + + if ( empty($method) ) + { + $service->sendResponse( + new PwgError(400, 'Missing "method" name') + ); + return; + } + $resp = $service->invoke($method, $params); + $service->sendResponse($resp); + } +} + +?> diff --git a/include/ws_protocols/xmlrpc_encoder.php b/include/ws_protocols/xmlrpc_encoder.php new file mode 100644 index 000000000..69919165e --- /dev/null +++ b/include/ws_protocols/xmlrpc_encoder.php @@ -0,0 +1,118 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +function xmlrpc_encode($data) +{ + switch (gettype($data)) + { + case 'boolean': + return '<boolean>'.($data ? '1' : '0').'</boolean>'; + case 'integer': + return '<int>'.$data.'</int>'; + case 'double': + return '<double>'.$data.'</double>'; + case 'string': + return '<string>'.htmlspecialchars($data).'</string>'; + case 'object': + case 'array': + $is_array = range(0, count($data) - 1) === array_keys($data); + if ($is_array) + { + $return = '<array><data>'."\n"; + foreach ($data as $item) + { + $return .= ' <value>'.xmlrpc_encode($item)."</value>\n"; + } + $return .= '</data></array>'; + } + else + { + $return = '<struct>'."\n"; + foreach ($data as $name => $value) + { + $name = htmlspecialchars($name); + $return .= " <member><name>$name</name><value>"; + $return .= xmlrpc_encode($value)."</value></member>\n"; + } + $return .= '</struct>'; + } + return $return; + } +} + +class PwgXmlRpcEncoder extends PwgResponseEncoder +{ + function encodeResponse($response) + { + $respClass = strtolower( get_class($response) ); + if ($respClass=='pwgerror') + { + $code = $response->code(); + $msg = htmlspecialchars($response->message()); + $ret = <<<EOD +<methodResponse> + <fault> + <value> + <struct> + <member> + <name>faultCode</name> + <value><int>{$code}</int></value> + </member> + <member> + <name>faultString</name> + <value><string>{$msg}</string></value> + </member> + </struct> + </value> + </fault> +</methodResponse> +EOD; + return $ret; + } + + parent::flattenResponse($response); + $ret = xmlrpc_encode($response); + $ret = <<<EOD +<methodResponse> + <params> + <param> + <value> + $ret + </value> + </param> + </params> +</methodResponse> +EOD; + return $ret; + } + + function getContentType() + { + return 'text/xml'; + } +} + +?> diff --git a/tools/prototype.js b/tools/prototype.js new file mode 100644 index 000000000..0e85338ba --- /dev/null +++ b/tools/prototype.js @@ -0,0 +1,1781 @@ +/* Prototype JavaScript framework, version 1.4.0 + * (c) 2005 Sam Stephenson <sam@conio.net> + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.4.0', + ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(eval); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + } +}); + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + inspect: function() { + return '#<Enumerable:' + this.toArray().inspect() + '>'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + shift: function() { + var result = this[0]; + for (var i = 0; i < this.length - 1; i++) + this[i] = this[i + 1]; + this.length--; + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval(this.header('X-JSON')); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(child); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); + } + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +}
\ No newline at end of file diff --git a/tools/ws.htm b/tools/ws.htm new file mode 100644 index 000000000..fdbd6a502 --- /dev/null +++ b/tools/ws.htm @@ -0,0 +1,346 @@ +<html> +<head> +<title>PWG web service explorer</title> +<script type="text/javascript" src="prototype.js" ></script> + +<script type="text/javascript"> +function setElementText(id, text) +{ + if (!text) text=""; + var elt = document.getElementById(id); + if (!elt) alert('setElementText '+id); + elt.innerHTML = text; +} + +function setVisibility(id, vis) +{ + document.getElementById(id).style.visibility = vis; +} + +function clearError() +{ + setElementText("error", ""); +} + +function dumpError(err) +{ + var s= 'Error '; + if ('string' === typeof err ) + s += err; + else + { + s += err.name+'<br/>'; + s += err.message; + if (err.stack!=null) + {//mozilla only + s += '<br/><small><pre>'+ err.stack + '</pre></small>'; + } + } + setElementText("error", s); +} + +var gServiceUrl; + +Ajax.Responders.register({ + +onException: function(req, err) { + try { + document.pwgError = err; + dumpError(err); + } + catch (e) + { + alert (err); + alert (err.message); + } +}, + +onComplete: function(req, transport) { + if (!req.responseIsSuccess()) + { + var s = 'Completion failure\n' + transport.status + ' ' + transport.statusText; + if (transport.status>=300) + { + s += '\n'; + s += transport.responseText.substr(0,1000); + } + dumpError(s); + } + } +} +); + + +function pwgGetJsonResult(transport) +{ + var resp; + try { + eval('resp = ' + transport.responseText); + } + catch (e) + { + var s = e.message; + s += '\n' + transport.responseText.substr(0,1000).escapeHTML(); + throw new Error( s ); + } + if (resp==null || resp.result==null || resp.stat==null || resp.stat!='ok') + { + var s = 'JSON evaluation error'; + if (resp) + { + if (resp.stat!=null) s+= '\n'+resp.stat; + if (resp.message!=null) s+= '\n'+ resp.message; + } + throw new Error(s); + } + return resp.result; +} + +function pwgChangeUrl() +{ + clearError(); + setVisibility("methodListWrapper", "hidden"); + setElementText("methodList", ""); + setVisibility("methodWrapper", "hidden"); + + gServiceUrl = $F('ws_url'); + + try { + var ajaxReq = new Ajax.Request( + gServiceUrl, + {method:'get', parameters:'format=json&method=reflection.getMethodList', + onSuccess: function (r) { onSuccess_getMethodList(r); } + } + ) + }catch (e) + { + dumpError(e); + } + return false; +} + +function onSuccess_getMethodList(transport) +{ + var result = pwgGetJsonResult(transport); + var ml = ''; + for (var i=0; i<result.methods.length; i++) + { + ml += '<li><a href="#" onclick="return pwgSelectMethod(this.innerHTML)">'+ result.methods[i]+'</a></li>'; + } + setElementText("methodList", ml); + setVisibility("methodListWrapper", "visible"); +} + +function pwgSelectMethod(method) +{ + clearError(); + setElementText("methodName", method); + setVisibility("methodDetailWrapper", "hidden"); + setVisibility("methodWrapper", "visible"); + + try { + + var ajaxReq = new Ajax.Request( + gServiceUrl, + {method:'get', parameters:'format=json&method=reflection.getMethodDetails&methodName='+method, + onSuccess: function (r) { onSuccess_getMethodDetails(r); } + } + ) + }catch (e) + { + dumpError( e ); + } + return false; +} + +function onSuccess_getMethodDetails(transport) +{ + var result = pwgGetJsonResult(transport); + var methodParamsElt = $("methodParams"); + while (methodParamsElt.tBodies[0].rows.length) + methodParamsElt.tBodies[0].deleteRow(methodParamsElt.tBodies[0].rows.length-1); + + if (result.params && result.params.length>0) + { + for (var i=0; i<result.params.length; i++) + { + var row = methodParamsElt.tBodies[0].insertRow(-1); + var isOptional = result.params[i].optional; + var defaultValue = result.params[i].defaultValue == null ? '' : result.params[i].defaultValue; + + row.insertCell(0).innerHTML = result.params[i].name; + row.insertCell(1).innerHTML = (isOptional ? 'optional':'required'); + row.insertCell(2).innerHTML = '<input id="methodParameterSend_'+i+'" type="checkbox" '+(isOptional ? '':'checked="checked"')+'/>'; + row.insertCell(3).innerHTML = '<input id="methodParameterName_'+i+'" type="hidden" value="'+result.params[i].name+'"/>' + +'<input id="methodParameterValue_'+i+'"" value="'+defaultValue+'" style="width:99%" onchange="$(\'methodParameterSend_'+i+'\').checked=true;"/>'; + } + } + setElementText("methodDescription", result.description); + setVisibility("methodDetailWrapper", "visible"); +} + +function pwgInvokeMethod() +{ + var method = document.getElementById('methodName').innerHTML; + + var reqUrl = gServiceUrl; + reqUrl += "?format="+$F('responseFormat'); + + if (document.getElementById('requestFormat').value == 'get') + { + reqUrl += "&method="+method; + var i=0; + do + { + var elt = document.getElementById('methodParameterName_'+i); + if (!elt) break; + if (document.getElementById('methodParameterSend_'+i).checked) + reqUrl += '&'+elt.value+'='+$F('methodParameterValue_'+i); + i++; + } + while (1); + document.getElementById("invokeFrame").src = reqUrl; + } + else + { + var form = document.getElementById("invokeForm"); + form.action = reqUrl; + var t = '<input type="hidden" name="'+'method'+'" value="'+method+'"/>'; + var i=0; + do + { + var elt = document.getElementById('methodParameterName_'+i); + if (!elt) break; + if (document.getElementById('methodParameterSend_'+i).checked) + t += '<input type="hidden" name="'+elt.value+'" value="'+$F('methodParameterValue_'+i)+'"/>'; + i++; + } + while (1); + form.innerHTML = t; + form.submit(); + } + return false; +} +</script> + + +<style> +#methodListWrapper { + width: 16em; + float: left; + display: inline; + visibility: hidden; +} + +#methodList { + padding-left: 15px; +} + +#methodWrapper { + margin-left: 16.5em; + visibility: hidden; +} + +#methodName { + margin-top: 0; + margin-bottom: 3px; +} + + +#error { + height: 90px; + overflow: scroll; + color: red; +} + +#methodParams { + border-collapse: collapse; +} +</style> + +</head> + + +<body> + +<div> + <label>PWG Web service url + <input name="ws_url" id="ws_url" size="64"/> +<script type="text/javascript"> + var match = document.location.toString().match(/^(https?.*\/)tools\/ws\.html?$/); + if (match!=null) $('ws_url').value = match[1]+'ws.php'; +</script> + </label> + <a href="#" onclick="return pwgChangeUrl();">Go!</a> +</div> + +<div id="error"> +</div> + +<div> + +<div id="methodListWrapper">Methods + <ul id="methodList"> + <li><a href="#" onclick="return pwgSelectMethod(this.innerHTML)">getVersion</a></li> + </ul> +</div> + +<div id="methodWrapper"> + <h2 id="methodName"></h2> + <div id="methodDetailWrapper"> + <div id="methodDescription"></div> + <table> + <tr> + <td>Request format:</td> + <td> + <select id="requestFormat"> + <option value="get" selected="selected">GET</option> + <option value="post">POST</option> + </select> + </td> + </tr> + + <tr> + <td>Response format:</td> + <td> + <select id="responseFormat"> + <option value="rest" selected="selected">REST (xml)</option> + <option value="json">JSON</option> + <option value="php">PHP serial</option> + <option value="xmlrpc">XML RPC</option> + </select> + </td> + </tr> + </table> + + <div id="methodParamsWrapper"> + <table id="methodParams" border="1" cellspacing="0" cellpadding="2px"> + <thead> + <tr> + <td style="width:150px">Parameter</td> + <td>Optional</td> + <td>Send</td> + <td style="width:160px">Value</td> + </tr> + </thead> + <tbody> + </tbody> + </table> + </div> + <a href="#" onclick="return pwgInvokeMethod()">Invoke</a> + + <div style="display:none"> + <!-- hiddenForm for POST --> + <form method="post" action="" target="invokeFrame" id="invokeForm"> + <input type="submit" value="submit"/> + </form> + </div> + + <iframe width="100%" height="400px" id="invokeFrame" name="invokeFrame"></iframe> + </div> <!-- methodDetailWrapper --> +</div> <!-- methodWrapper --> + +</div> + +</body> +</html> @@ -0,0 +1,183 @@ +<?php +// +-----------------------------------------------------------------------+ +// | PhpWebGallery - a PHP based picture gallery | +// | Copyright (C) 2003-2007 PhpWebGallery Team - http://phpwebgallery.net | +// +-----------------------------------------------------------------------+ +// | branch : BSF (Best So Far) +// | file : $URL: svn+ssh://rvelices@svn.gna.org/svn/phpwebgallery/trunk/action.php $ +// | last update : $Date: 2006-12-21 18:49:12 -0500 (Thu, 21 Dec 2006) $ +// | last modifier : $Author: rvelices $ +// | revision : $Rev: 1678 $ +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +define ('PHPWG_ROOT_PATH', './'); + +include_once(PHPWG_ROOT_PATH.'include/common.inc.php'); +include_once(PHPWG_ROOT_PATH.'include/ws_core.inc.php'); + +function ws_addDefaultMethods( $arr ) +{ + include_once(PHPWG_ROOT_PATH.'include/ws_functions.inc.php'); + $service = &$arr[0]; + $service->addMethod('pwg.getVersion', 'ws_getVersion', null, + 'retrieves the PWG version'); + + $service->addMethod('pwg.categories.getImages', 'ws_categories_getImages', + array( + 'cat_id'=>array('default'=>0, 'flags'=>WS_PARAM_FORCE_ARRAY), + 'recursive'=>array('default'=>false), + 'per_page' => array('default'=>100), + 'page' => array('default'=>0), + 'order' => array('default'=>null), + 'f_min_rate' => array( 'default'=> null ), + 'f_max_rate' => array( 'default'=> null ), + 'f_min_hit' => array( 'default'=> null ), + 'f_max_hit' => array( 'default'=> null ), + 'f_min_date_available' => array( 'default'=> null ), + 'f_max_date_available' => array( 'default'=> null ), + 'f_min_date_created' => array( 'default'=> null ), + 'f_max_date_created' => array( 'default'=> null ), + 'f_min_ratio' => array( 'default'=> null ), + 'f_max_ratio' => array( 'default'=> null ), + 'f_with_thumbnail' => array( 'default'=> false ), + ), + 'Returns elements for the corresponding categories. +<br/><b>cat_id</b> can be empty if <b>recursive</b> is true. Can be sent as an array. +<br/><b>order</b> comma separated fields for sorting (file,id, average_rate,...)' + ); + + $service->addMethod('pwg.categories.getList', 'ws_categories_getList', + array( + 'cat_id' => array('default'=>0), + 'recursive' => array('default'=>false), + 'public' => array('default'=>false), + ), + 'retrieves a list of categories' ); + + $service->addMethod('pwg.images.getInfo', 'ws_images_getInfo', + array('image_id'), + 'retrieves information about the given photo' ); + + $service->addMethod('pwg.session.getStatus', 'ws_session_getStatus', null, '' ); + $service->addMethod('pwg.session.login', 'ws_session_login', + array('username', 'password'), + 'POST method only' ); + $service->addMethod('pwg.session.logout', 'ws_session_logout', null, ''); + + $service->addMethod('pwg.tags.getList', 'ws_tags_getList', + array('sort_by_counter' => array('default' =>false) ), + 'retrieves a list of available tags'); + $service->addMethod('pwg.tags.getImages', 'ws_tags_getImages', + array( + 'tag_id'=>array('default'=>null, 'flags'=>WS_PARAM_FORCE_ARRAY ), + 'tag_url_name'=>array('default'=>null, 'flags'=>WS_PARAM_FORCE_ARRAY ), + 'tag_name'=>array('default'=>null, 'flags'=>WS_PARAM_FORCE_ARRAY ), + 'tag_mode_and'=>array('default'=>false), + 'per_page' => array('default'=>100), + 'page' => array('default'=>0), + 'order' => array('default'=>null), + 'f_min_rate' => array( 'default'=> null ), + 'f_max_rate' => array( 'default'=> null ), + 'f_min_hit' => array( 'default'=> null ), + 'f_max_hit' => array( 'default'=> null ), + 'f_min_date_available' => array( 'default'=> null ), + 'f_max_date_available' => array( 'default'=> null ), + 'f_min_date_created' => array( 'default'=> null ), + 'f_max_date_created' => array( 'default'=> null ), + 'f_min_ratio' => array( 'default'=> null ), + 'f_max_ratio' => array( 'default'=> null ), + 'f_with_thumbnail' => array( 'default'=> false ), + ), + 'Returns elements for the corresponding tags. Note that tag_id, tag_url_name, tag_name an be arrays. Fill at least one of them. ' + ); +} + +add_event_handler('ws_add_methods', 'ws_addDefaultMethods' ); + +$requestFormat = null; +$responseFormat = null; + +if ( isset($_GET['format']) ) +{ + $responseFormat = $_GET['format']; +} + +if ( isset($HTTP_RAW_POST_DATA) ) +{ + $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA); + if ( strncmp($HTTP_RAW_POST_DATA, '<?xml', 5) == 0 ) + { + } + else + { + $requestFormat = "json"; + } +} +else +{ + $requestFormat = "rest"; +} + +if ( !isset($responseFormat) and isset($requestFormat) ) +{ + $responseFormat = $requestFormat; +} + +$service = new PwgServer(); + +if (!is_null($requestFormat)) +{ + $handler = null; + switch ($requestFormat) + { + case 'rest': + include_once(PHPWG_ROOT_PATH.'include/ws_protocols/rest_handler.php'); + $handler = new PwgRestRequestHandler(); + break; + } + $service->setHandler($requestFormat, $handler); +} + +if (!is_null($responseFormat)) +{ + $encoder = null; + switch ($responseFormat) + { + case 'rest': + include_once(PHPWG_ROOT_PATH.'include/ws_protocols/rest_encoder.php'); + $encoder = new PwgRestEncoder(); + break; + case 'php': + include_once(PHPWG_ROOT_PATH.'include/ws_protocols/php_encoder.php'); + $encoder = new PwgSerialPhpEncoder(); + break; + case 'json': + include_once(PHPWG_ROOT_PATH.'include/ws_protocols/json_encoder.php'); + $encoder = new PwgJsonEncoder(); + break; + case 'xmlrpc': + include_once(PHPWG_ROOT_PATH.'include/ws_protocols/xmlrpc_encoder.php'); + $encoder = new PwgXmlRpcEncoder(); + break; + } + $service->setEncoder($responseFormat, $encoder); +} + +$page['root_path']=get_host_url().cookie_path(); +$service->run(); + +?> |