diff options
Diffstat (limited to '')
-rw-r--r-- | BSF/tools/config_local.inc.php | 8 | ||||
-rw-r--r-- | BSF/tools/create_listing_file.php | 1696 | ||||
-rw-r--r-- | BSF/tools/create_listing_file_local.inc.php | 14 | ||||
-rw-r--r-- | BSF/tools/fill_history.pl | 336 | ||||
-rw-r--r-- | BSF/tools/index.php | 30 | ||||
-rw-r--r-- | BSF/tools/local-layout.css | 10 | ||||
-rw-r--r-- | BSF/tools/metadata.php | 97 | ||||
-rw-r--r-- | BSF/tools/prototype.js | 3277 | ||||
-rw-r--r-- | BSF/tools/pwg_rel_create.sh | 61 | ||||
-rw-r--r-- | BSF/tools/release_creation.readme | 50 | ||||
-rw-r--r-- | BSF/tools/ws.htm | 402 |
11 files changed, 5981 insertions, 0 deletions
diff --git a/BSF/tools/config_local.inc.php b/BSF/tools/config_local.inc.php new file mode 100644 index 000000000..6e74538fb --- /dev/null +++ b/BSF/tools/config_local.inc.php @@ -0,0 +1,8 @@ +<?php +// this file is provided as an example. It does not modify the configuration +// as long as it remains in "tools" directory. Move it to "include" +// directory if you want to modify default configuration. + +$conf['prefix_thumbnail'] = 'thumb_'; +$conf['show_gt'] = true; +?>
\ No newline at end of file diff --git a/BSF/tools/create_listing_file.php b/BSF/tools/create_listing_file.php new file mode 100644 index 000000000..8b1ee34ee --- /dev/null +++ b/BSF/tools/create_listing_file.php @@ -0,0 +1,1696 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Piwigo - a PHP based picture gallery | +// +-----------------------------------------------------------------------+ +// | Copyright(C) 2008 Piwigo Team http://piwigo.org | +// | Copyright(C) 2003-2008 PhpWebGallery Team http://phpwebgallery.net | +// | Copyright(C) 2002-2003 Pierrick LE GALL http://le-gall.net/pierrick | +// +-----------------------------------------------------------------------+ +// | 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. | +// +-----------------------------------------------------------------------+ + +// +-----------------------------------------------------------------------+ +// | User configuration | +// +-----------------------------------------------------------------------+ + +// ****** Gallery configuration ****** // +// Script version +$conf['version'] = 'branch 1.7'; + +// URL of main gallery +// Example : http://www.my.domain/my/directory +$conf['gallery'] = 'http://demo.piwigo.net/'; + +// prefix for thumbnails in "thumbnail" sub directories +$conf['prefix_thumbnail'] = 'TN-'; + +// $conf['file_ext'] lists all extensions (case insensitive) allowed +// for your Piwigo installation +$conf['file_ext'] = array('jpg','JPG','jpeg','JPEG', + 'png','PNG','gif','GIF','mpg','zip', + 'avi','mp3','ogg'); + + +// $conf['picture_ext'] must be a subset of $conf['file_ext'] +$conf['picture_ext'] = array('jpg','JPG','jpeg','JPEG', + 'png','PNG','gif','GIF'); + +// ****** Time limitation functionality ****** // +// max execution time before refresh in seconds +$conf['max_execution_time'] = (5*ini_get('max_execution_time'))/6; // 25 seconds with default PHP configuration +// force the use of refresh method +// in order to have live informations +// or +// to fix system witch are not safe mode but not autorized set_time_limit +$conf['force_refresh_method'] = true; + +// refresh delay is seconds +$conf['refresh_delay'] = 0; + +// ****** EXIF support functionality ****** // +// $conf['use_exif'] set to true if you want to use Exif information +$conf['use_exif'] = true; + +// use_exif_mapping: same behaviour as use_iptc_mapping +$conf['use_exif_mapping'] = array( + 'date_creation' => 'DateTimeOriginal' + ); + +// ****** IPTC support functionality ****** // +// $conf['use_iptc'] set to true if you want to use IPTC informations of the +// element according to get_sync_iptc_data function mapping, otherwise, set +// to false +$conf['use_iptc'] = false; + +// use_iptc_mapping : in which IPTC fields will Piwigo find image +// information ? This setting is used during metadata synchronisation. It +// associates a piwigo_images column name to a IPTC key +$conf['use_iptc_mapping'] = array( + 'keywords' => '2#025', + 'date_creation' => '2#055', + 'author' => '2#122', + 'name' => '2#005', + 'comment' => '2#120'); + +// ****** Directory protection functionality ****** // +// Define if directories have to be protected if they are not +$conf['protect'] = false; + +// true/false : show/hide warnings +$conf['protect_warnings'] = true; + +// ****** Thumbnails generation functionality ****** // +// Define if images have to be reduced if they are not +$conf['thumbnail'] = false; + +// Define method to generate thumbnails : +// - fixed (width and height required); +// - width (only width required); +// - height (only height required); +// - ratio (only ratio is required) +// - exif (no other parameter required) +$conf['thumbnail_method'] = 'ratio'; + +// Height in pixels (greater than 0) +$conf['thumbnail_height'] = 128; + +// Width in pixels (greater than 0) +$conf['thumbnail_width'] = 128; + +// Ratio between original and thumbnail size (strictly between 0 and 1) +$conf['thumbnail_ratio'] = 0.2; + +// Define thumbnail format : jpeg, png or gif (will be verified) +$conf['thumbnail_format'] = 'jpeg'; + +// ****** Directory mapping ****** // +// directories names +$conf['thumbs'] = 'thumbnail'; // thumbnails +$conf['high'] = 'pwg_high'; // high resolution +$conf['represent'] = 'pwg_representative'; // non pictures representative files + + +// +-----------------------------------------------------------------------+ +// | Overload configurations | +// +-----------------------------------------------------------------------+ +@include(dirname(__FILE__).'/'.basename(__FILE__, '.php').'_local.inc.php'); + + +// +-----------------------------------------------------------------------+ +// | Advanced script configuration | +// +-----------------------------------------------------------------------+ + +// url of icon directory in yoga template +$pwg_conf['icon_dir'] = $conf['gallery'].'/template/yoga/icon/'; + +// list of actions managed by this script +$pwg_conf['scan_action'] = array('clean', 'test', 'generate'); + +// url of this script +$pwg_conf['this_url'] = + (empty($_SERVER['HTTPS']) ? 'http://' : 'https://') + .str_replace(':'.$_SERVER['SERVER_PORT'], '', $_SERVER['HTTP_HOST']) + .($_SERVER['SERVER_PORT'] != 80 ? ':'.$_SERVER['SERVER_PORT'] : '') + .$_SERVER['PHP_SELF']; + +// list of reserved directory names +$pwg_conf['reserved_directory_names'] = array($conf['thumbs'], $conf['high'], $conf['represent'], ".", "..", ".svn"); + +// content of index.php generated in protect action +$pwg_conf['protect_content'] = '<?php header("Location: '.$conf['gallery'].'") ?>'; + +// backup of PHP safe_mode INI parameter (used for time limitation) +$pwg_conf['safe_mode'] = (ini_get('safe_mode') == '1') ? true : false; + +// This parameter will be fixed in pwg_init() +$pwg_conf['gd_version_major'] = ''; +$pwg_conf['gd_version_full'] = ''; +$pwg_conf['gd_supported_format'] = array(); + +// +-----------------------------------------------------------------------+ +// | Functions | +// +-----------------------------------------------------------------------+ + +/** + * write line in log file + * + * @param string line + * @return string + */ +function pwg_log($line) +{ + $log_file = fopen(__FILE__.'.log', 'a'); + fwrite($log_file, $line); + fclose($log_file); +} + +/** + * Check web server graphical capabilities + * + * @return string + */ +function pwg_check_graphics() +{ + //~ pwg_log('>>>>> pwg_check_graphics() >>>>>'."\n"); + + global $conf, $pwg_conf; + $log = ''; + + // Verify gd library for thumbnail generation + if ($conf['thumbnail'] and !is_callable('gd_info')) + { + $log .= ' <code class="warning">Warning -</code> Your server can not generate thumbnails. Thumbnail creation switched off.<br />'."\n"; + // Switch off thumbnail generation + $conf['thumbnail'] = false; + return $log; + } + + // Verify thumnail format + if ($conf['thumbnail']) + { + $info = gd_info(); + + // Backup GD major version + $pwg_conf['gd_version_full'] = ereg_replace('[[:alpha:][:space:]()]+', '', $info['GD Version']); + list($pwg_conf['gd_version_major']) = preg_split('/[.]+/', $pwg_conf['gd_version_full']); + + // Backup input/output format support + array_push($pwg_conf['gd_supported_format'], $info['JPG Support'] ? 'jpeg' : NULL); + array_push($pwg_conf['gd_supported_format'], $info['PNG Support'] ? 'png' : NULL); + array_push($pwg_conf['gd_supported_format'], ($info['GIF Read Support'] and $info['GIF Create Support']) ? 'gif' : NULL); + + // Check output format support + if (!in_array($conf['thumbnail_format'], $pwg_conf['gd_supported_format'])) + { + $log .= ' <code class="warning">Warning -</code> Your server does not support thumbnail\'s <code>'; + $log .= $conf['thumbnail_format'].'</code> format. Thumbnail creation switched off.<br />'."\n"; + } + + switch ($conf['thumbnail_method']) + { + case 'exif': + { + // exif_thumbnail() must be callable + if (!is_callable('exif_thumbnail')) + { + $log .= ' <code class="warning">Warning -</code> Your server does not support thumbnail creation through EXIF datas. Thumbnail creation switched off.<br />'."\n"; + } + break; + } + case 'fixed': + { + // $conf['thumbnail_width'] > 0 + if (!is_numeric($conf['thumbnail_width']) or $conf['thumbnail_width'] <= 0) + { + $log .= ' <code class="failure">Failure -</code> Bad value <code>thumbnail_width = '; + $log .= var_export($conf['thumbnail_width'], true).'</code>. Thumbnail creation switched off.<br />'."\n"; + } + // $conf['thumbnail_height'] > 0 + if (!is_numeric($conf['thumbnail_height']) or $conf['thumbnail_height'] <= 0) + { + $log .= ' <code class="failure">Failure -</code> Bad value <code>thumbnail_height = '; + $log .= var_export($conf['thumbnail_height'], true).'</code>. Thumbnail creation switched off.<br />'."\n"; + } + break; + } + case 'ratio': + { + // 0 < $conf['thumbnail_ratio'] < 1 + if (!is_numeric($conf['thumbnail_ratio']) or $conf['thumbnail_ratio'] <= 0 or $conf['thumbnail_ratio'] >= 1) + { + $log .= ' <code class="failure">Failure -</code> Bad value <code>thumbnail_ratio = '; + $log .= var_export($conf['thumbnail_ratio'], true).'</code>. Thumbnail creation switched off.<br />'."\n"; + } + break; + } + case 'width': + { + // $conf['thumbnail_width'] > 0 + if (!is_numeric($conf['thumbnail_width']) or $conf['thumbnail_width'] <= 0) + { + $log .= ' <code class="failure">Failure -</code> Bad value <code>thumbnail_width = '; + $log .= var_export($conf['thumbnail_width'], true).'</code>. Thumbnail creation switched off.<br />'."\n"; + } + break; + } + case 'height': + { + // $conf['thumbnail_height'] > 0 + if (!is_numeric($conf['thumbnail_height']) or $conf['thumbnail_height'] <= 0) + { + $log .= ' <code class="failure">Failure -</code> Bad value <code>thumbnail_height = '; + $log .= var_export($conf['thumbnail_height'], true).'</code>. Thumbnail creation switched off.<br />'."\n"; + } + break; + } + default: + { + // unknown method + $log .= ' <code class="failure">Failure -</code> Bad value <code>thumbnail_method = '; + $log .= var_export($conf['thumbnail_method'], true).'</code>. Thumbnail creation switched off.<br />'."\n"; + break; + } + } + + if (strlen($log)) + { + $conf['thumbnail'] = false; + } + } + + //~ pwg_log('<<<<< pwg_check_graphics() returns '.var_export($log, TRUE).' <<<<<'."\n"); + return $log; +} + +/** + * returns xml </dirX> lines + * + * @param integer $dir_start + * @param integer $dir_number + * @return string + */ +function pwg_close_level($dir_start, $dir_number) +{ + //~ pwg_log('>>>>> pwg_close_level($dir_start = '.var_export($dir_start, TRUE).', $dir_number = '.var_export($dir_number, TRUE).') >>>>>'."\n"); + + $lines =''; + do + { + $lines .= str_repeat(' ', 2*$dir_start).'</dir'.$dir_start.">\n"; + $dir_number--; + $dir_start--; + } + while(($dir_number > 0) && ($dir_start >= 0)); + + //~ pwg_log('<<<<< pwg_close_level returns '.var_export($lines, TRUE).' <<<<<'."\n"); + return $lines; +} + +/** + * return a cleaned IPTC value + * + * @param string value + * @return string + */ +function pwg_clean_iptc_value($value) +{ + //~ pwg_log('>>>>> pwg_clean_iptc_value ($value = '.var_export($value, TRUE).') >>>>>'."\n"); + + // strip leading zeros (weird Kodak Scanner software) + while (isset($value[0]) and $value[0] == chr(0)) + { + $value = substr($value, 1); + } + // remove binary nulls + $value = str_replace(chr(0x00), ' ', $value); + + //~ pwg_log('<<<<< pwg_clean_iptc_value() returns '.var_export($value, TRUE).' <<<<<'."\n"); + return $value; +} + +/** + * returns informations from IPTC metadata, mapping is done at the beginning + * of the function + * + * @param string $filename + * @param string $map + * @return array + */ +function pwg_get_iptc_data($filename, $map) +{ + //~ pwg_log('>>>>> pwg_get_iptc_data ($filename = '.var_export($filename, TRUE).', $map = '.var_export($map, TRUE).') >>>>>'."\n"); + + $result = array(); + + // Read IPTC data + $iptc = array(); + + $imginfo = array(); + getimagesize($filename, $imginfo); + + if (isset($imginfo['APP13'])) + { + $iptc = iptcparse($imginfo['APP13']); + if (is_array($iptc)) + { + $rmap = array_flip($map); + foreach (array_keys($rmap) as $iptc_key) + { + if (isset($iptc[$iptc_key][0])) + { + if ($iptc_key == '2#025') + { + $value = implode(',', array_map('pwg_clean_iptc_value', $iptc[$iptc_key])); + } + else + { + $value = pwg_clean_iptc_value($iptc[$iptc_key][0]); + } + + foreach (array_keys($map, $iptc_key) as $pwg_key) + { + $result[$pwg_key] = $value; + } + } + } + } + } + + //~ pwg_log('<<<<< pwg_get_iptc_data() returns '.var_export($result, TRUE).' <<<<<'."\n"); + return $result; +} + +/** + * returns informations from IPTC metadata + * + * @param string $file + * @return array iptc + */ +function pwg_get_sync_iptc_data($file) +{ + //~ pwg_log('>>>>> pwg_get_sync_iptc_data ($file = '.var_export($file, TRUE).') >>>>>'."\n"); + + global $conf; + + $map = $conf['use_iptc_mapping']; + $datefields = array('date_creation', 'date_available'); + + $iptc = pwg_get_iptc_data($file, $map); + + foreach ($iptc as $pwg_key => $value) + { + if (in_array($pwg_key, $datefields)) + { + if ( preg_match('/(\d{4})(\d{2})(\d{2})/', $value, $matches)) + { + $value = $matches[1].'-'.$matches[2].'-'.$matches[3]; + } + } + if ($pwg_key == 'keywords') + { + // official keywords separator is the comma + $value = preg_replace('/[.;]/', ',', $value); + $value = preg_replace('/^,+|,+$/', '', $value); + } + $iptc[$pwg_key] = htmlentities($value); + } + + $iptc['keywords'] = isset($iptc['keywords']) ? implode(',', array_unique(explode(',', $iptc['keywords']))) : NULL; + + //~ pwg_log('<<<<< pwg_get_sync_iptc_data() returns '.var_export($iptc, TRUE).' <<<<<'."\n"); + return $iptc; +} + +/** + * return extension of the representative file + * + * @param string $file_dir + * @param string $file_short + * @return string + */ +function pwg_get_representative_ext($file_dir, $file_short) +{ + //~ pwg_log('>>>>> pwg_get_representative_ext($file_dir = '.var_export($file_dir, TRUE).', $file_short = '.var_export($file_short, TRUE).') >>>>>'."\n"); + + global $conf; + + $rep_ext = ''; + foreach ($conf['picture_ext'] as $ext) + { + if (file_exists($file_dir.'/'.$conf['represent'].'/'.$file_short.'.'.$ext)) + { + $rep_ext = $ext; + break; + } + } + + //~ pwg_log('<<<<< pwg_get_representative_ext() returns '.var_export($rep_ext, TRUE).' <<<<<'."\n"); + return $rep_ext; +} + +/** + * return 'true' if high resolution picture exists else '' + * + * @param string $file_dir + * @param string $file_base + * @return boolean + */ +function pwg_get_high($file_dir, $file_base) +{ + //~ pwg_log('>>>>> pwg_get_high($file = '.var_export($file_dir, TRUE).', $line = '.var_export($file_base, TRUE).') >>>>>'."\n"); + + global $conf; + + $high = false; + if (file_exists($file_dir.'/'.$conf['high'].'/'.$file_base)) + { + $high = true; + } + + //~ pwg_log('<<<<< pwg_get_high() returns '.var_export($high, TRUE).' <<<<<'."\n"); + return $high; +} + +/** + * return filename without extension + * + * @param string $filename + * @return string + */ +function pwg_get_filename_wo_extension($filename) +{ + //~ pwg_log('>>>>> _get_filename_wo_extension($filename = '.var_export($filename, TRUE).') >>>>>'."\n"); + + $short_name = substr($filename, 0, strrpos($filename, '.')); + + //~ pwg_log('<<<<< _get_filename_wo_extension() returns '.var_export($short_name, TRUE).' <<<<<'."\n"); + return $short_name; +} + +/** + * return extension of the thumbnail and complete error_log + * + * @param string $file_dir + * @param string $file_short + * @param string $file_ext + * @param string &$error_log + * @return string + */ +function pwg_get_thumbnail_ext($file_dir, $file_short, $file_ext, &$error_log) +{ + //~ pwg_log('>>>>> pwg_get_thumbnail_ext($file_dir = '.var_export($file_dir, TRUE).', $file_short = '.var_export($file_short, TRUE).') >>>>>'."\n"); + + global $conf; + + $thumb_ext = ''; + foreach ($conf['picture_ext'] as $ext) + { + if (file_exists($file_dir.'/'.$conf['thumbs'].'/'.$conf['prefix_thumbnail'].$file_short.'.'.$ext)) + { + $thumb_ext = $ext; + break; + } + } + + if ($thumb_ext == '') + { + if ($conf['thumbnail']) + { + $log = pwg_icon_file($file_dir, $file_short, $file_ext); + if (strpos($log, 'success')) + { + $thumb_ext = $conf['thumbnail_format']; + } + $error_log .= $log; + } + } + + //~ pwg_log('<<<<< pwg_get_thumbnail_ext() returns '.var_export($thumb_ext, TRUE).' <<<<<'."\n"); + return $thumb_ext; +} + + +/** + * return error logs + * + * @param string $file_dir + * @param string $file_short + * @param string $file_ext + * @return string + */ +function pwg_icon_file($file_dir, $file_short, $file_ext) +{ + //~ pwg_log('>>>>> pwg_icon_file($file_dir = '.var_export($file_dir, TRUE).', $file_short = '.var_export($file_short, TRUE).') >>>>>'."\n"); + + global $conf, $pwg_conf; + + $error_log = ''; + + // Get original properties (width, height) + if ($image_size = getimagesize($file_dir.'/'.$file_short.'.'.$file_ext)) + { + $src_width = $image_size[0]; + $src_height = $image_size[1]; + } + else + { + $error_log .= ' <code class="failure">Failure -</code> Can not generate icon for <code>'; + $error_log .= $file_dir.'/'.$file_short.'.'.$file_ext.'</code>'; + $error_log .= ' <img src="'.$pwg_conf['icon_dir'].'add_tag.png" title="width/height are unreadable" /><br />'."\n"; + return $error_log; + } + + // Check input format + $dst_format = $conf['thumbnail_format']; + $src_format = ($file_ext == 'jpg' or $file_ext == 'JPG') ? 'jpeg' : strtolower($file_ext); + if (!in_array($src_format, $pwg_conf['gd_supported_format'])) + { + $error_log .= ' <code class="failure">Failure -</code> Can not generate icon for <code>'; + $error_log .= $file_dir.'/'.$file_short.'.'.$file_ext.'</code>'; + $error_log .= ' <img src="'.$pwg_conf['icon_dir'].'add_tag.png" title="format not supported" /><br />'."\n"; + return $error_log; + } + + // Calculate icon properties (width, height) + switch ($conf['thumbnail_method']) + { + case 'fixed': + { + $dst_width = $conf['thumbnail_width']; + $dst_height = $conf['thumbnail_height']; + break; + } + case 'width': + { + $dst_width = $conf['thumbnail_width']; + $dst_height = $dst_width * $src_height / $src_width; + break; + } + case 'height': + { + $dst_height = $conf['thumbnail_height']; + $dst_width = $dst_height * $src_width / $src_height; + break; + } + case 'ratio': + { + $dst_width = round($src_width * $conf['thumbnail_ratio']); + $dst_height = round($src_height * $conf['thumbnail_ratio']); + break; + } + case 'exif': + default: + { + // Nothing to do + } + } + + // Creating icon + if ($conf['thumbnail_method'] == 'exif') + { + $src = exif_thumbnail($file_dir.'/'.$file_short.'.'.$file_ext, $width, $height, $imagetype); + if ($src === false) + { + $error_log .= ' <code class="failure">Failure -</code> No EXIF thumbnail in <code>'; + $error_log .= $file_dir.'/'.$file_short.'.'.$file_ext.'</code><br />'."\n"; + return $error_log; + } + $dst = imagecreatefromstring($src); + if ($src === false) + { + $error_log .= ' <code class="failure">Failure -</code> EXIF thumbnail format not supported in <code>'; + $error_log .= $file_dir.'/'.$file_short.'.'.$file_ext.'</code><br />'."\n"; + return $error_log; + } + } + else + { + if (($pwg_conf['gd_version_major'] != 2)) // or ($conf['thumbnail_format'] == 'gif')) + { + $dst = imagecreate($dst_width, $dst_height); + } + else + { + $dst = imagecreatetruecolor($dst_width, $dst_height); + } + $src = call_user_func('imagecreatefrom'.$src_format, $file_dir.'/'.$file_short.'.'.$file_ext); + if (!$src) + { + $error_log .= ' <code class="failure">Failure -</code> Internal error for <code>imagecreatefrom'.$src_format.'()</code>'; + $error_log .= 'with <code>'.$file_dir.'/'.$file_short.'.'.$file_ext.'</code><br />'."\n"; + return $error_log; + } + + if (($pwg_conf['gd_version_major'] != 2)) + { + if (!imagecopyresized($dst, $src, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height)) + { + $error_log .= ' <code class="failure">Failure -</code> Internal error for <code>imagecopyresized()</code>'; + $error_log .= 'with <code>'.$file_dir.'/'.$file_short.'.'.$file_ext.'</code><br />'."\n"; + return $error_log; + } + } + else + { + if (!imagecopyresampled($dst, $src, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height)) + { + $error_log .= ' <code class="failure">Failure -</code> Internal error for <code>imagecopyresampled()</code>'; + $error_log .= 'with <code>'.$file_dir.'/'.$file_short.'.'.$file_ext.'</code><br />'."\n"; + return $error_log; + } + } + } + + if (!call_user_func('image'.$dst_format, $dst, $file_dir.'/'.$conf['thumbs'].'/'.$conf['prefix_thumbnail'].$file_short.'.'.$conf['thumbnail_format'])) + { + $error_log .= ' <code class="failure">Failure -</code> Can not write <code>'; + $error_log .= $file_dir.'/'.$conf['thumbs'].'/'.$conf['prefix_thumbnail'].$file_short.'.'.$file_ext.'</code> to generate thumbnail<br />'."\n"; + return $error_log; + } + + $error_log .= ' <code class="success">Success -</code> Thumbnail generated for <code>'; + $error_log .= $file_dir.'/'.$file_short.'.'.$file_ext.'</code><br />'."\n"; + + //~ pwg_log('<<<<< pwg_icon_file() returns '.var_export($error_log, TRUE).' <<<<<'."\n"); + return $error_log; +} + +/** + * completes xml line <element .../> and returns error log + * + * @param string $file + * @param string &$line + * @return string + */ +function pwg_scan_file($file_full, &$line) +{ + //~ pwg_log('>>>>> pwg_scan_file($file = '.var_export($file_full, TRUE).', $line = '.var_export($line, TRUE).') >>>>>'."\n"); + + global $conf, $pwg_conf; + + $error_log =''; + + $file_base = basename($file_full); + $file_short = pwg_get_filename_wo_extension($file_base); + $file_ext = pwg_get_file_extension($file_base); + $file_dir = dirname($file_full); + + $element['file'] = $file_base; + $element['path'] = dirname($pwg_conf['this_url']).substr($file_dir, 1).'/'.$file_base; + + if (in_array($file_ext, $conf['picture_ext'])) + { + // Here we scan a picture : thumbnail is mandatory, high is optionnal, representative is not scanned + $element['tn_ext'] = pwg_get_thumbnail_ext($file_dir, $file_short, $file_ext, $error_log); + if ($element['tn_ext'] != '') + { + // picture has a thumbnail, get image width, heigth, size in Mo + $element['filesize'] = floor(filesize($file_full) / 1024); + if ($image_size = getimagesize($file_full)) + { + $element['width'] = $image_size[0]; + $element['height'] = $image_size[1]; + } + + // get high resolution + if (pwg_get_high($file_dir, $file_base)) + { + $element['has_high'] = 'true'; + + $high_file = $file_dir.'/'.$conf['high'].'/'.$file_base; + $element['high_filesize'] = floor(filesize($high_file) / 1024); + } + + // get EXIF meta datas + if ($conf['use_exif']) + { + // Verify activation of exif module + if (extension_loaded('exif')) + { + if ($exif = read_exif_data($file_full)) + { + foreach ($conf['use_exif_mapping'] as $pwg_key => $exif_key ) + { + if (isset($exif[$exif_key])) + { + if ( in_array($pwg_key, array('date_creation','date_available') ) ) + { + if (preg_match('/^(\d{4}):(\d{2}):(\d{2})/', $exif[$exif_key], $matches)) + { + $element[$pwg_key] = $matches[1].'-'.$matches[2].'-'.$matches[3]; + } + } + else + { + $element[$pwg_key] = $exif[$exif_key]; + } + } + } + } + } + } + + // get IPTC meta datas + if ($conf['use_iptc']) + { + $iptc = pwg_get_sync_iptc_data($file_full); + if (count($iptc) > 0) + { + foreach (array_keys($iptc) as $key) + { + $element[$key] = addslashes($iptc[$key]); + } + } + } + + } + else + { + $error_log .= ' <code class="failure">Failure -</code> Thumbnail is missing for <code>'.$file_dir.'/'.$file_base.'</code>'; + $error_log .= ' <img src="'.$pwg_conf['icon_dir'].'add_tag.png" title="'.$file_dir.'/thumbnail/'.$conf['prefix_thumbnail'].$file_short; + $error_log .= '.xxx ('.implode(', ', $conf['picture_ext']).')" /><br />'."\n"; + } + } + else + { + // Here we scan a non picture file : thumbnail and high are unused, representative is optionnal + $element['tn_ext'] = pwg_get_thumbnail_ext($file_dir, $file_short, $file_ext, $log); + $ext = pwg_get_representative_ext($file_dir, $file_short); + if ($ext != '') + { + $element['representative_ext'] = $ext; + } + } + + if (strlen($error_log) == 0) + { + $line = pwg_get_indent('element').'<element '; + foreach($element as $key => $value) + { + $line .= $key.'="'.$value.'" '; + } + $line .= '/>'."\n"; + } + + //~ pwg_log('<<<<< pwg_scan_file() returns '.var_export($error_log, TRUE).' <<<<<'."\n"); + return $error_log; +} + +/** + * returns current level in tree + * + * @return integer + */ +function pwg_get_level($dir) +{ + //~ pwg_log('>>>>> pwg_get_level($dir = '.var_export($dir, TRUE).') >>>>>'."\n"); + + $level = substr_count($dir, '/') - 1; // -1 because of ./ at the beginning of path + + //~ pwg_log('<<<<< pwg_get_level() returns '.var_export($level, TRUE).' <<<<<'."\n"); + return $level; +} + +/** + * returns indentation of element + * + * @param string $element_type : 'root', 'element', 'dir' + * @return string + */ +function pwg_get_indent($element_type) +{ + //~ pwg_log('>>>>> pwg_get_indent($element_type = '.var_export($element_type, TRUE).') >>>>>'."\n"); + + $level = substr_count($_SESSION['scan_list_fold'][0], '/') - 1; // because of ./ at the beginning + switch($element_type) + { + case 'dir' : + { + $indent = str_repeat(' ', 2*pwg_get_level($_SESSION['scan_list_fold'][0])); + break; + } + case 'root' : + { + $indent = str_repeat(' ', 2*pwg_get_level($_SESSION['scan_list_fold'][0])+2); + break; + } + case 'element' : + { + $indent = str_repeat(' ', 2*pwg_get_level($_SESSION['scan_list_fold'][0])+4); + break; + } + default : + { + $indent = ''; + break; + } + } + + //~ pwg_log('<<<<< pwg_get_indent() returns '.var_export(strlen($indent), TRUE).' spaces <<<<<'."\n"); + return $indent; +} + +/** + * create index.php in directory and reserved sub_directories, return logs + * + * @param string dir + * @return string + */ +function pwg_protect_directories($directory) +{ + //~ pwg_log('>>>>> pwg_protect_directories($directory = '.var_export($directory, true).') >>>>>'."\n"); + + global $conf; + + $error_log = ''; + $dirlist = array($directory, $directory.'/'.$conf['thumbs'], $directory.'/'.$conf['high'], $directory.'/'.$conf['represent']); + + foreach ($dirlist as $dir) + { + if (file_exists($dir)) + { + if (!file_exists($dir.'/index.php')) + { + $file = @fopen($dir.'/index.php', 'w'); + if ($file != false) + { + fwrite($file, $pwg_conf['protect_content']); // the return code should be verified + $error_log .= ' <code class="success">Success -</code> index.php created in directory <a href="'.$dir.'">'.$dir."</a><br />\n"; + } + else + { + $error_log .= ' <code class="failure">Failure -</code> Can not create index.php in directory <code>'.$dir."</code><br />\n"; + } + } + else + { + if ($conf['protect_warnings']) + { + $error_log .= ' <code class="warning">Warning -</code> index.php already exists in directory <a href="'.$dir.'">'.$dir."</a><br />\n"; + } + } + } + } + + //~ pwg_log('<<<<< pwg_protect_directories() returns '.var_export($error_log, true).' <<<<<'."\n"); + return $error_log; +} + +/** + * returns file extension (.xxx) + * + * @param string $file + * @return string + */ +function pwg_get_file_extension($file) +{ + //~ pwg_log('>>>>> pwg_get_file_extension($file = '.var_export($file, true).') >>>>>'."\n"); + + $ext = substr(strrchr($file, '.'), 1, strlen ($file)); + + //~ pwg_log('<<<<< pwg_get_file_extension() returns '.var_export($ext, true).' <<<<<'."\n"); + return $ext; +} + +/** + * completes directory list of supported files and returns error logs + * + * @param string $directory + * @return string + */ +function pwg_get_file_list($directory) +{ + //~ pwg_log('>>>>> pwg_get_file_list($directory = '.var_export($directory, true).') >>>>>'."\n"); + + global $conf, $pwg_conf; + + $errorLog = ''; + $dir = opendir($directory); + while (($file = readdir($dir)) !== false) + { + switch (filetype($directory."/".$file)) + { + case 'file' : + { + if (in_array(pwg_get_file_extension($file), $conf['file_ext'])) + { + // The file pointed is a regular file with a supported extension + array_push($_SESSION['scan_list_file'], $directory.'/'.$file); + //~ pwg_log('--->> Push in $_SESSION[scan_list_file] value "'.$directory.'/'.$file.'"'."\n"); + if (!preg_match('/^[a-zA-Z0-9-_.]+$/', $file)) + { + $errorLog .= ' <code class="failure">Failure -</code> Invalid file name for <code>'.$file.'</code> in <code>'.$directory.'</code>'; + $errorLog .= ' <img src="'.$pwg_conf['icon_dir'].'add_tag.png"'; + $errorLog .= ' title="Name should be composed of letters, figures, -, _ or . ONLY" /><br />'."\n"; + } + } + break; // End of filetype FILE + } + case 'dir' : + { + if(!in_array($file, $pwg_conf['reserved_directory_names'])) + { + // The file pointed is a directory but neither system directory nor reserved by PWG + array_push($_SESSION['scan_list_fold'], $directory.'/'.$file); + //~ pwg_log('--->> Push in $_SESSION[scan_list_fold] value "'.$directory.'/'.$file.'"'."\n"); + if (!preg_match('/^[a-zA-Z0-9-_.]+$/', $file)) + { + $errorLog .= ' <code class="failure">Failure -</code> Invalid directory name for <code>'.$directory.'/'.$file.'</code>'; + $errorLog .= ' <img src="'.$pwg_conf['icon_dir'].'add_tag.png"'; + $errorLog .= ' title="Name should be composed of letters, figures, -, _ or . ONLY" /><br />'."\n"; + } + } + break; // End of filetype DIR + } + case 'fifo' : + case 'char' : + case 'block' : + case 'link' : + case 'unknown': + default : + { + // PWG does not manage these cases + break; + } + } + } + closedir($dir); + + //~ pwg_log('<<<<< pwg_get_file_list() returns '.var_export($errorLog, true).' <<<<<'."\n"); + + return $errorLog; +} + +/** + * returns a float value coresponding to the number of seconds since the + * unix epoch (1st January 1970) and the microseconds are precised : + * e.g. 1052343429.89276600 + * + * @return float + */ +function pwg_get_moment() +{ + //~ pwg_log('>>>>> pwg_get_moment() >>>>>'."\n"); + + $t1 = explode(' ', microtime()); + $t2 = explode('.', $t1[0]); + $t2 = $t1[1].'.'.$t2[1]; + + //~ pwg_log('<<<<< pwg_get_moment() returns '.var_export($t2, true).' <<<<<'."\n"); + return $t2; +} + +/** + * return true if HTTP_REFERER and PHP_SELF are similar + * + * return boolean + */ +function pwg_referer_is_me() +{ + global $pwg_conf; + + //~ pwg_log('>>>>> pwg_referer_is_me() >>>>>'."\n"); + + $response = false; + $caller = (isset($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : ''; + + if (strcasecmp($pwg_conf['this_url'], $caller) == 0) { + $response = true; + } + + //~ pwg_log('<<<<< pwg_referer_is_me() returns '.var_export($response, true).' <<<<<'."\n"); + return $response; +} + +// +-----------------------------------------------------------------------+ +// | pwg_<ACTION>_<STEP> Functions | +// +-----------------------------------------------------------------------+ + +function pwg_test_start() +{ + //~ pwg_log('>>>>> pwg_test_start() >>>>>'."\n"); + + global $g_message, $conf; + + if (isset($_REQUEST['version'])) + { + if ($_REQUEST['version'] != $conf['version']) + { + $g_message = '0'; + } + else + { + $g_message = '1'; + } + } + else + { + $g_message = '1'; + } + $_SESSION['scan_step'] = 'exit'; + + //~ pwg_log('<<<<< pwg_test_start() <<<<<'."\n"); +} + +function pwg_test_exit() +{ + //~ pwg_log('>>>>> pwg_test_exit() >>>>>'."\n"); + + global $g_header, $g_message, $g_footer, $conf, $pwg_conf; + + if (pwg_referer_is_me()) + { + $g_header = ' : <span class="success">Test</span>'."\n"; + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'."\n"; + + // Write version + $g_message = ' <h3>Script version</h3>'."\n"; + $g_message .= ' This script is tagged : <code class="failure">'.$conf['version'].'</code>'."\n"; + // write GD support + if (!is_callable('gd_info')) + { + $g_message .= ' <code class="failure">Failure -</code> Your server can not generate imagess<br />'."\n"; + } + else + { + $info = gd_info(); + $gd_full_version = ereg_replace('[[:alpha:][:space:]()]+', '', $info['GD Version']); + list($gd_version) = preg_split('/[.]+/', $gd_full_version); + + $g_message .= ' <h3>Image generation</h3>'."\n"; + $g_message .= ' <code class="success">Success -</code> Your server can generate images<br />'."\n"; + $g_message .= ' <code class="warning">Warning -</code> Your server support GD'.$gd_version.' (v'.$gd_full_version.')<br />'."\n"; + $format_list = array(); + $format = ($info['GIF Create Support']) ? '<code>gif</code>' : NULL; + array_push($format_list, $format); + $format = ($info['JPG Support']) ? '<code>jpg</code>' : NULL; + array_push($format_list, $format); + $format = ($info['PNG Support']) ? '<code>png</code>' : NULL; + array_push($format_list, $format); + $g_message .= ' <code class="warning">Warning -</code> Your server support format: '.implode(', ', $format_list)."\n"; + } + + $g_message .= ' <h3>Directory parsing</h3>'."\n"; + if ($pwg_conf['safe_mode']) + { + $g_message .= ' <code class="warning">Warning -</code> Your server does not support to resize execution time'."\n"; + } + else + { + $g_message .= ' <code class="success">Success -</code> Your server supports to resize execution time'."\n"; + } + } + else + { + // compare version in GET parameter with $conf['version'] + if ($g_message == '1') + { + exit('<pre>PWG-INFO-2: test successful</pre>'); + } + else + { + exit('<pre>PWG-ERROR-4: Piwigo versions differs</pre>'); + } + } + + //~ pwg_log('<<<<< pwg_test_exit() <<<<<'."\n"); +} + +function pwg_clean_start() +{ + //~ pwg_log('>>>>> pwg_clean_start() >>>>>'."\n"); + + global $g_message; + + if(@unlink('./listing.xml')) + { + $g_message = '1'; + } + else + { + $g_message = '0'; + } + + $_SESSION['scan_step'] = 'exit'; + + //~ pwg_log('<<<<< pwg_clean_start() <<<<<'."\n"); +} + +function pwg_clean_exit() +{ + //~ pwg_log('>>>>> pwg_clean_exit() >>>>>'."\n"); + + global $g_header, $g_message, $g_footer, $conf, $pwg_conf; + + if(pwg_referer_is_me()) + { + $g_header = ' : <span class="success">Clean</span>'; + if ($g_message == '1') + { + $g_message = ' <code class="success">Success -</code> <code>listing.xml</code> file deleted'."\n"; + } + else + { + $g_message = ' <code class="failure">Failure -</code> <code>listing.xml</code> does not exist or is read only'."\n"; + } + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'; + } + else + { + if ($g_message == '1') + { + exit('<pre>PWG-INFO-3 : listing.xml file deleted</pre>'); + } + else + { + exit('<pre>PWG-ERROR-3 : listing.xml does not exist</pre>'); + } + } + + //~ pwg_log('<<<<< pwg_clean_exit() <<<<<'."\n"); +} + +function pwg_generate_start() +{ + //~ pwg_log('>>>>> pwg_generate_start() >>>>>'."\n"); + //~ pwg_log("GENARATE start >>>\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE start >>>\n"); + + global $g_listing, $pwg_conf, $conf; + + // Flush line <informations> + $xml_header_url = dirname($pwg_conf['this_url']); + $xml_header_date = date('Y-m-d'); + $xml_header_version = htmlentities($conf['version']); + + $attrs = array(); + if ($conf['use_iptc']) + { + $attrs = array_merge($attrs, array_keys($conf['use_iptc_mapping']) ); + } + if ($conf['use_exif']) + { + $attrs = array_merge($attrs, array_keys($conf['use_exif_mapping']) ); + } + $xml_header_metadata = implode(',',array_unique($attrs)); + + $xml_header = '<informations'; + $xml_header .= ' generation_date="'.$xml_header_date.'"'; + $xml_header .= ' phpwg_version="'.$xml_header_version.'"'; + $xml_header .= ' metadata="'.$xml_header_metadata.'"'; + $xml_header .= ' url="'.$xml_header_url.'"'; + $xml_header .= '>'."\n"; + + fwrite($g_listing, $xml_header); + + // Initialization of directory and file lists + $_SESSION['scan_list_fold'] = array(); + $_SESSION['scan_list_file'] = array(); + $_SESSION['scan_logs'] .= pwg_get_file_list('.'); + sort($_SESSION['scan_list_fold']); + + // Erase first file list because root directory does not contain images. + $_SESSION['scan_list_file'] = array(); + + // What are we doing at next step + if(count($_SESSION['scan_list_fold']) > 0) + { + $_SESSION['scan_step'] = 'list'; + } + else + { + $_SESSION['scan_step'] = 'stop'; + } + + //~ pwg_log("GENARATE start <<<\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE start <<<\n"); + //~ pwg_log('<<<<< pwg_generate_start() <<<<<'."\n"); +} + +function pwg_generate_list() +{ + //~ pwg_log('>>>>> pwg_generate_list() >>>>>'."\n"); + //~ pwg_log("GENARATE list >>>\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE list >>>\n"); + + global $g_listing; + + // Flush line <dirX name=""> in xml file + $dirname = basename($_SESSION['scan_list_fold'][0]); + $line = pwg_get_indent('dir').'<dir'.pwg_get_level($_SESSION['scan_list_fold'][0]).' name="'.$dirname.'">'."\n"; + fwrite($g_listing, $line); + + // Get list of files and directories + $_SESSION['scan_logs'] .= pwg_get_file_list($_SESSION['scan_list_fold'][0]); + sort($_SESSION['scan_list_fold']); // Mandatory to keep the tree order + sort($_SESSION['scan_list_file']); // Easier to read when sorted + + // Flush line <root> + $line = pwg_get_indent('root').'<root>'."\n"; + fwrite($g_listing, $line); + + // What are we doing at next step + $_SESSION['scan_step'] = 'scan'; + + //~ pwg_log("GENARATE list <<<\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE list <<<\n"); + //~ pwg_log('<<<<< pwg_generate_list() <<<<<'."\n"); +} + +function pwg_generate_scan() +{ + //~ pwg_log('>>>>> pwg_generate_scan() >>>>>'."\n"); + //~ pwg_log("GENARATE scan >>>\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE scan >>>\n"); + + global $g_listing, $conf; + + while (pwg_continue() and count($_SESSION['scan_list_file']) > 0) + { + $line = ''; + $_SESSION['scan_logs'] .= pwg_scan_file($_SESSION['scan_list_file'][0], $line); + + if (strlen($line) > 0) + { + fwrite($g_listing, $line); + } + //~ pwg_log('---<< Pull of $_SESSION[scan_list_file] value "'.$_SESSION['scan_list_file'][0].'"'."\n"); + array_shift($_SESSION['scan_list_file']); + $_SESSION['scan_cnt_file']++; + } + + if (count($_SESSION['scan_list_file']) <= 0) + { + $_SESSION['scan_step'] = 'prot'; + } + + //~ pwg_log("GENERATE scan <<<\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE scan <<<\n"); + //~ pwg_log('<<<<< pwg_generate_scan() <<<<<'."\n"); +} + +function pwg_generate_prot() +{ + //~ pwg_log('>>>>> pwg_generate_prot() >>>>>'."\n"); + //~ pwg_log("GENARATE prot >>>\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE prot >>>\n"); + + global $conf, $g_listing; + + // Flush line </root> + $line = pwg_get_indent('root').'</root>'."\n"; + fwrite($g_listing, $line); + + if ($conf['protect']) + { + $_SESSION['scan_logs'] .= pwg_protect_directories($_SESSION['scan_list_fold'][0]); + } + + // How many directories to close + $current_level = pwg_get_level($_SESSION['scan_list_fold'][0]); + if (isset($_SESSION['scan_list_fold'][1])) + { + //~ pwg_log('---<< Pull of $_SESSION[scan_list_fold] value "'.$_SESSION['scan_list_fold'][0].'"'."\n"); + array_shift($_SESSION['scan_list_fold']); + $_SESSION['scan_cnt_fold']++; + $next_level = pwg_get_level($_SESSION['scan_list_fold'][0]); + $_SESSION['scan_step'] = 'list'; + } + else + { + $next_level = -1; + $_SESSION['scan_cnt_fold']++; + $_SESSION['scan_step'] = 'stop'; + } + + if ($current_level == $next_level) + { + fwrite($g_listing, pwg_close_level($current_level, 1)); + } + else + { + if (($current_level > $next_level)) + { + fwrite($g_listing, pwg_close_level($current_level, $current_level-$next_level+1)); + } // Nothing to do if current_level < next_level + } + + //~ pwg_log("GENERATE prot <<<\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE prot <<<\n"); + //~ pwg_log('<<<<< pwg_generate_prot() <<<<<'."\n"); +} + +function pwg_generate_stop() +{ + //~ pwg_log('>>>>> pwg_generate_stop() >>>>>'."\n"); + //~ pwg_log("GENARATE stop >>>\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE stop >>>\n"); + + global $pwg_conf, $g_listing, $g_header, $g_message, $g_footer; + + // Flush line </informations> + fwrite($g_listing, '</informations>'."\n"); + + // backup error log before cleaning session + $time_elapsed = number_format(pwg_get_moment() - $_SESSION['scan_time'], 3, '.', ' '); + + $g_header = ' : <span class="success">Generate</span>'; + $g_message = ' <div>'."\n".$_SESSION['scan_logs'].' </div>'."\n"; + $g_message .= ' <div><code class="success">'.$_SESSION['scan_cnt_fold'].'</code> directories parsed<br />'."\n"; + $g_message .= ' <code class="success">'.$_SESSION['scan_cnt_file'].'</code> files scanned</div>'."\n"; + $g_message .= ' <div>View <a href="listing.xml">listing.xml</a></div>'."\n"; + $g_message .= ' <div style="{text-align: right;}">Listing generated in : <code>'.$time_elapsed.' s</code></div>'; + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'; + + // What are we doing at next step + $_SESSION['scan_step'] = 'exit'; + + //~ pwg_log("GENARATE stop <<<\n".var_export($_SESSION['scan_list_fold'], true)."\n".var_export($_SESSION['scan_list_file'], true)."\nGENERATE stop <<<\n"); + //~ pwg_log('<<<<< pwg_generate_stop() <<<<<'."\n"); +} + +// +-----------------------------------------------------------------------+ +// | ALWAYS CALLED FUNCTIONS | +// +-----------------------------------------------------------------------+ + +/** + * This function check step and time ellapsed to determine end of loop + * + * @return bool + */ +function pwg_continue() +{ + //~ pwg_log('>>>>> pwg_continue() >>>>>'."\n"); + + global $conf, $pwg_conf, $g_refresh, $g_header, $g_message, $g_footer, $start_time; + + if (!isset($_SESSION['scan_step']) or $_SESSION['scan_step'] == 'exit') + { + // evident end of process + $return = false; + } + else + { + if ($pwg_conf['safe_mode'] or $conf['force_refresh_method']) + { + // can not reset the time + $time_elapsed = pwg_get_moment() - $start_time; + if ($time_elapsed < $conf['max_execution_time']) + { + $return = true; + } + else + { + $start_time = $_SESSION['scan_time']; + $formated_time = number_format(pwg_get_moment() - $start_time, 3, '.', ' '); + + $g_refresh = '<meta http-equiv="Refresh" content="'.$conf['refresh_delay'].'">'."\n"; + $g_header = ' : <span class="success">'.ucfirst($_SESSION['scan_action']).'</span>'; + $g_message = ''; + if ($_SESSION['scan_cnt_fold'] != 0) + { + $g_message .= '<code class="success">'.$_SESSION['scan_cnt_fold'].'</code> directories scanned<br />'."\n"; + } + if ($_SESSION['scan_cnt_file'] != 0) + { + $g_message .= '<code class="success">'.$_SESSION['scan_cnt_file'].'</code> files scanned<br />'."\n"; + } + $nb = count($_SESSION['scan_list_fold']); + if ($nb > 0) + { + $g_message .= '<code class="warning">'.$nb.'</code> directories to scan<br />'."\n"; + } + $nb = count($_SESSION['scan_list_file']); + if ($nb > 0) + { + $g_message .= '<code class="warning">'.$nb.'</code> files to scan<br />'."\n"; + } + $g_message .= ' <div style="{text-align: right;}">Time elapsed : <code>'.$formated_time.' s</code></div>'; + $g_footer = '<a href="'.$pwg_conf['this_url'].'?action='.$_SESSION['scan_action'].'" title="Continue"><img src="'.$pwg_conf['icon_dir'].'right.png" /></a>'."\n"; + + $return = false; + } + } + else + { + // reset the time + set_time_limit(intval(ini_get('max_execution_time'))); + $return = true; + } + } + //~ pwg_log('<<<<< pwg_continue() returns '.var_export($return, true).' <<<<<'."\n"); + + return $return; +} + +/** + * This function : + * -> Verify the script call + * -> Lock the script + * -> Open listing.xml if action is 'generate' + * -> Initialize output and session variables + * + * @return nothing + */ +function pwg_init() +{ + //~ pwg_log('>>>>> pwg_init() >>>>>'."\n"); + + global $g_message, $g_listing, $g_footer, $conf, $pwg_conf, $start_time; + $init_message = ''; + + // Lock other script sessions, this lock will be remove during 'exit' step + if (!isset($_SESSION['scan_step'])) + { + $fp = @fopen(__FILE__.'.lock', 'x+'); // return false if __FILE__.lock exists or if cannot create + if ($fp == false) + { + $g_header = $_SESSION['scan_action']; + $g_message = ' <code class="failure">Failure -</code> Another script is running'; + $g_message .= ' <img src="'.$pwg_conf['icon_dir'].'add_tag.png" title="Delete file '.__FILE__.'.lock and retry" />'; + $g_message .= "\n"; + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'."\n"; + $_SESSION['scan_step'] = 'exit'; + //~ pwg_log('<<<<< pwg_init() failure <<<<<'."\n"); + return; + } + else + { + fwrite($fp, session_id()); // Writing session_id to trace lock + fclose($fp); + $_SESSION['scan_step'] = 'init'; + } + } + + // Verify and backup parameter action. This backup will be removed during step 'exit' + if (isset($_REQUEST['action'])) + { + if (in_array($_REQUEST['action'], $pwg_conf['scan_action'])) + { + if (isset($_SESSION['scan_action'])) + { + if ($_SESSION['scan_action'] != $_REQUEST['action']) + { + // Fatal error + $g_message = ' <code class="failure">Failure -</code> Parameter <code>action</code> differs between url and session'; + $g_message .= "\n"; + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'."\n"; + $_SESSION['scan_step'] = 'exit'; + //~ pwg_log('<<<<< pwg_init() failure <<<<<'."\n"); + return; + } + } + else + { + $_SESSION['scan_action'] = $_REQUEST['action']; + } + } + else + { + // Fatal error + $g_message = ' <code class="failure">Failure -</code> Problem with <code>action</code> parameter'; + $g_message .= ' <img src="'.$pwg_conf['icon_dir'].'add_tag.png" title="empty, '.implode(', ', $pwg_conf['scan_action']).'" />'; + $g_message .= "\n"; + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'."\n"; + $_SESSION['scan_step'] = 'exit'; + //~ pwg_log('<<<<< pwg_init() failure <<<<<'."\n"); + return; + } + } + else + { + // Here we are on welcome page + $g_message = ' <ul>'."\n"; + $g_message .= ' <li><a href="'.$pwg_conf['this_url'].'?action=test" title="Display/Compare script version">Test</a></li>'."\n"; + $g_message .= ' <li><a href="'.$pwg_conf['this_url'].'?action=clean" title="Delete listing.xml if exists">Clean</a></li>'."\n"; + $g_message .= ' <li><a href="'.$pwg_conf['this_url'].'?action=generate" title="Scan all images from this directory and write informations in listing.xml">Listing</a></li>'."\n"; + $g_message .= ' </ul>'."\n"; + $g_footer = '<a href="'.$conf['gallery'].'/admin.php?page=site_manager" title="Main gallery :: site manager">'; + $g_footer .= '<img src="'.$pwg_conf['icon_dir'].'home.png" /></a>'."\n"; + $_SESSION['scan_step'] = 'exit'; + $_SESSION['scan_action'] = ''; + } + + // Actions to do at the init of generate + if ($_SESSION['scan_action'] == 'generate') + { + // Open XML file + $mode = ($_SESSION['scan_step'] == 'init') ? 'w' : 'a'; // Erase old listing.xml at the beginning of generation (mode w) + $g_listing = @fopen('listing.xml', $mode); + if ($g_listing === false) + { + $g_header = $_SESSION['scan_action']; + $g_message = ' <code class="failure">Failure -</code> Can not write file <code>listing.xml</code>'; + $g_message .= "\n"; + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'."\n"; + $_SESSION['scan_step'] = 'exit'; + //~ pwg_log('<<<<< pwg_init() failure <<<<<'."\n"); + return; + } + + // Check graphical capabilities + $init_message = pwg_check_graphics(); + } + + // Initializing session counters. This counters will be completely unset during step 'exit' + if ($_SESSION['scan_step'] == 'init') + { + $_SESSION['scan_list_file'] = array(); + $_SESSION['scan_list_fold'] = array(); + $_SESSION['scan_cnt_file'] = 0; + $_SESSION['scan_cnt_fold'] = 0; + $_SESSION['scan_time'] = $start_time; + $_SESSION['scan_step'] = 'start'; + $_SESSION['scan_logs'] = $init_message; + } + + //~ pwg_log('<<<<< pwg_init() success <<<<<'."\n"); +} + +/** + * This function : + * -> Close listing.xml if action is 'generate' + * -> Unlock the script + * -> Erase session variables + * + * @return nothing + */ +function pwg_exit() +{ + //~ pwg_log('>>>>> pwg_exit() >>>>>'."\n"); + + global $g_listing; + + // Close XML file + if ($_SESSION['scan_action'] == 'generate' and $g_listing != false) + { + fclose($g_listing); + } + + // Unlock script + unlink(__FILE__.'.lock'); + + // Erase session counters + unset($_SESSION['scan_list_file']); + unset($_SESSION['scan_list_fold']); + unset($_SESSION['scan_cnt_file']); + unset($_SESSION['scan_cnt_fold']); + unset($_SESSION['scan_time']); + unset($_SESSION['scan_step']); + $local_action = $_SESSION['scan_action']; + unset($_SESSION['scan_action']); + unset($_SESSION['scan_logs']); + session_destroy(); + + // Call specific action post process + if (is_callable('pwg_'.$local_action.'_exit')) + { + call_user_func('pwg_'.$local_action.'_exit'); + } + + //~ pwg_log('<<<<< pwg_exit() <<<<<'."\n"); +} + +// +-----------------------------------------------------------------------+ +// | Script | +// +-----------------------------------------------------------------------+ +session_save_path('.'); +session_start(); + +$start_time = pwg_get_moment(); + +// Initializing message for web page +$g_refresh = ''; +$g_header = ''; +$g_message = ''; +$g_footer = ''; +$g_listing = ''; + +pwg_init(); + +while(pwg_continue()) +{ + if (is_callable('pwg_'.$_SESSION['scan_action'].'_'.$_SESSION['scan_step'])) + { + call_user_func('pwg_'.$_SESSION['scan_action'].'_'.$_SESSION['scan_step']); // Run the step : start, list, scan, stop are available + } + else + { + $g_header = $_SESSION['scan_action']; + $g_message = ' <code class="failure">Failure -</code> INTERNAL STEP ERROR : <code>pwg_'.$_SESSION['scan_action'].'_'.$_SESSION['scan_step'].'()</code> undefined'; + $g_message .= "\n"; + $g_footer = '<a href="'.$pwg_conf['this_url'].'" title="Main menu"><img src="'.$pwg_conf['icon_dir'].'up.png" /></a>'."\n"; + $_SESSION['scan_step'] = 'exit'; + } +} + +if ($_SESSION['scan_step'] == 'exit') +{ + pwg_exit(); +} + +?> +<html> + <head> + <?php echo $g_refresh; ?> + <title>Manage remote gallery</title> + </head> + <style type="text/css"> + code {font-weight: bold} + img {border-style: none; vertical-align: middle} + ul {list-style-image: url(<?php echo $pwg_conf['icon_dir']; ?>add_tag.png)} + .success {color: green} + .warning {color: orange} + .failure {color: red} + .header {text-align: center; font-variant: small-caps; font-weight: bold;} + .p {color: #F93;} + .w {color: #ccc;} + .g {color: #69C;} + .pwg {text-decoration: none; border-bottom-style: dotted; border-bottom-width: 1px;} + .content {width: 75%; position: absolute; top: 10%; left: 12%;} + .footer {text-align: right;} + .pwg_block {float: left;} + </style> + <body> + <div class="content"> + <fieldset class="header"> + <span class="p">Pi</span> + <span class="w">wi</span> + <span class="g">go</span> + remote site<? echo $g_header; ?> + </fieldset> + <fieldset> +<?php echo $g_message; ?> + </fieldset> + <fieldset class="footer"> + <div class="pwg_block"> + Powered by <a href="http://piwigo.org" class="pwg"><span class="p">Pi</span><span class="w">wi</span><span class="g">go</span></a> + </div> + <?php echo $g_footer; ?> + </fieldset> + </div> + </body> +</html> diff --git a/BSF/tools/create_listing_file_local.inc.php b/BSF/tools/create_listing_file_local.inc.php new file mode 100644 index 000000000..8d914cf88 --- /dev/null +++ b/BSF/tools/create_listing_file_local.inc.php @@ -0,0 +1,14 @@ +<?php +// this file is provided as an example. +// Move it to "create_listing_file.php" +// directory if you want to modify default configuration. + +// URL of main gallery +// Example : http://www.my.domain/my/directory +$conf['gallery'] = 'http://demo.piwigo.net/'; + +$conf['file_ext'] = array_merge($conf['file_ext'], array('flv', 'FLV')); + +$conf['force_refresh_method'] = true; + +?>
\ No newline at end of file diff --git a/BSF/tools/fill_history.pl b/BSF/tools/fill_history.pl new file mode 100644 index 000000000..01adacfd2 --- /dev/null +++ b/BSF/tools/fill_history.pl @@ -0,0 +1,336 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Getopt::Long; +use DBI; +use File::Basename; +use Time::Local; +use List::Util qw/shuffle min/; + +my %opt; +GetOptions( + \%opt, + qw/dbname=s dbuser=s dbpass=s prefix=s + total=i start_date=s end_date=s + help/ + ); + +if (defined($opt{help})) +{ + print <<FIN; + +Fill the user comments table of Piwigo. + +Usage: pwg_fill_comments.pl --dbname=<database_name> + --dbuser=<username> + --dbpass=<password> + --tagfile=<tags filename> + [--prefix=<tables prefix>] + [--help] + +--dbname, --dbuser and --dbpass are connexion parameters. + +--tagfile + +--prefix : determines the prefix for your table names. + +--help : show this help + +FIN + + exit(0); +} + +my $usage = "\n\n".basename($0)." --help for help\n\n"; + +foreach my $option (qw/dbname dbuser dbpass start_date end_date/) { + if (not exists $opt{$option}) { + die 'Error: '.$option.' is a mandatory option', $usage; + } +} + +$opt{prefix} = 'piwigo_' if (not defined $opt{prefix}); +my $dbh = DBI->connect( + 'DBI:mysql:'.$opt{dbname}, + $opt{dbuser}, + $opt{dbpass} +); + +my $query; +my $sth; + + +# retrieve all available users +$query = ' +SELECT id + FROM '.$opt{prefix}.'users +'; +my @user_ids = keys %{ $dbh->selectall_hashref($query, 'id') }; + +# set a list of IP addresses for each users +my %user_IPs = (); +foreach my $user_id (@user_ids) { + for (1 .. 1 + int rand 5) { + push( + @{ $user_IPs{$user_id} }, + join( + '.', + map {1 + int rand 255} 1..4 + ) + ); + } +} + +# use Data::Dumper; print Dumper(\%user_IPs); exit(); + +# start and end dates +my ($year,$month,$day,$hour,$min,$sec) + = ($opt{start_date} =~ m/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/); +my $start_unixtime = timelocal(0,0,0,$day,$month-1,$year); + +($year,$month,$day,$hour,$min,$sec) + = ($opt{end_date} =~ m/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/); +my $end_unixtime = timelocal(0,0,0,$day,$month-1,$year); + +# "tags from image" and "images from tag" +$query = ' +SELECT image_id, tag_id + FROM '.$opt{prefix}.'image_tag +'; +my %image_tags = (); +my %tag_images = (); +my %related_tag_of = (); +my @tags = (); +$sth = $dbh->prepare($query); +$sth->execute(); +while (my $row = $sth->fetchrow_hashref()) { + push( + @{$image_tags{$row->{image_id}}}, + $row->{tag_id} + ); + + push( + @{$tag_images{$row->{tag_id}}}, + $row->{image_id} + ); + + push ( + @tags, + $row->{tag_id} + ); +} + +# foreach my $tag_id (keys %tag_images) { +# printf( +# "tag %5u: %5u images\n", +# $tag_id, +# scalar @{$tag_images{$tag_id}} +# ); +# } +# exit(); + +# use Data::Dumper; print Dumper(\%tag_images); exit(); + +# categories from image_id +$query = ' +SELECT image_id, category_id + FROM '.$opt{prefix}.'image_category +'; +my %image_categories = (); +my %category_images =(); +my %categories = (); +$sth = $dbh->prepare($query); +$sth->execute(); +while (my $row = $sth->fetchrow_hashref()) { + push( + @{$image_categories{$row->{image_id}}}, + $row->{category_id} + ); + + push( + @{$category_images{$row->{category_id}}}, + $row->{image_id} + ); + + $categories{ $row->{category_id} }++; +} + +my @images = keys %image_categories; +my @categories = keys %categories; + +# use Data::Dumper; +# print Dumper(\%image_categories); + +my @sections = ( + 'categories', +# 'tags', +# 'search', +# 'list', +# 'favorites', +# 'most_visited', +# 'best_rated', +# 'recent_pics', +# 'recent_cats', +); + +my @inserts = (); + +USER : foreach my $user_id (@user_ids) { + print 'user_id: ', $user_id, "\n"; + + my $current_unixtime = $start_unixtime; + my @IPs = @{ $user_IPs{$user_id} }; + + VISIT : foreach my $visit_num (1..100_000) { + print 'visit: ', $visit_num, "\n"; + my @temp_inserts = (); + my $IP = (@IPs)[int rand @IPs]; + my $current_page = 0; + my $visit_size = 10 + int rand 90; + $current_unixtime+= 86_400 + int rand(86_400 * 30); + + my $section = $sections[int rand scalar @sections]; + # print 'section: ', $section, "\n"; + + push( + @temp_inserts, + { + section => $section, + } + ); + + if ($section eq 'categories') { + CATEGORY : foreach my $category_id ( + (shuffle @categories)[1..int rand scalar @categories] + ) { + # print 'category: ', $category_id, "\n"; + push( + @temp_inserts, + { + category_id => $category_id, + } + ); + + my @images = @{$category_images{$category_id}}; + IMAGE : foreach my $image_id ( + (shuffle @images)[1..min(10, scalar @images)] + ) { + push( + @temp_inserts, + { + category_id => $category_id, + image_id => $image_id, + } + ); + } + } + } + + if ($section eq 'tags') { + # TODO + } + + # transfert @temp_inserts to @inserts + print 'temp_insert size: ', scalar @temp_inserts, "\n"; + foreach my $temp_insert (@temp_inserts) { + $current_unixtime+= 5 + int rand 25; + next VISIT if ++$current_page == $visit_size; + last VISIT if $current_unixtime >= $end_unixtime; + + my $date = unixtime_to_mysqldate($current_unixtime); + my $time = unixtime_to_mysqltime($current_unixtime); + + my ($year, $month, $day) = split '-', $date; + my ($hour) = split ':', $time; + + $temp_insert->{date} = $date; + $temp_insert->{time} = $time; + $temp_insert->{year} = $year; + $temp_insert->{month} = $month; + $temp_insert->{day} = $day; + $temp_insert->{hour} = $hour; + $temp_insert->{IP} = $IP; + $temp_insert->{section} = $section; + $temp_insert->{user_id} = $user_id; + + push(@inserts, $temp_insert); + } + } +} + +@inserts = sort { + $a->{date} cmp $b->{date} + or $a->{time} cmp $b->{time} +} @inserts; + +if (scalar @inserts) { + my @columns = + qw/ + date time year month day hour + user_id IP + section category_id image_id + /; + + my $question_marks_string = '('; + $question_marks_string.= join( + ',', + map {'?'} @columns + ); + $question_marks_string.= ')'; + + my $query = ' +INSERT INTO '.$opt{prefix}.'history + ('.join(', ', @columns).') + VALUES +'; + $query.= join( + ',', + map {$question_marks_string} (1 .. scalar @inserts) + ); + $query.= ' +'; + + # print $query, "\n"; + + my @values = (); + + foreach my $insert (@inserts) { + push( + @values, + map { + $insert->{$_} + } @columns + ); + } + + $sth = $dbh->prepare($query); + $sth->execute(@values) + or die 'cannot execute insert query'; +} + +sub unixtime_to_mysqldate { + my ($unixtime) = @_; + + ($sec,$min,$hour,$day,$month,$year) = localtime($unixtime); + + return sprintf( + '%d-%02d-%02d', + $year+1900, + $month+1, + $day, + ); +} + +sub unixtime_to_mysqltime { + my ($unixtime) = @_; + + ($sec,$min,$hour,$day,$month,$year) = localtime($unixtime); + + return sprintf( + '%d:%d:%d', + $hour, + $min, + $sec + ); +} diff --git a/BSF/tools/index.php b/BSF/tools/index.php new file mode 100644 index 000000000..c15b15795 --- /dev/null +++ b/BSF/tools/index.php @@ -0,0 +1,30 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Piwigo - a PHP based picture gallery | +// +-----------------------------------------------------------------------+ +// | Copyright(C) 2008 Piwigo Team http://piwigo.org | +// | Copyright(C) 2003-2008 PhpWebGallery Team http://phpwebgallery.net | +// | Copyright(C) 2002-2003 Pierrick LE GALL http://le-gall.net/pierrick | +// +-----------------------------------------------------------------------+ +// | 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. | +// +-----------------------------------------------------------------------+ + +// Recursive call +$url = '../'; +header( 'Request-URI: '.$url ); +header( 'Content-Location: '.$url ); +header( 'Location: '.$url ); +exit(); +?> diff --git a/BSF/tools/local-layout.css b/BSF/tools/local-layout.css new file mode 100644 index 000000000..3017c7a06 --- /dev/null +++ b/BSF/tools/local-layout.css @@ -0,0 +1,10 @@ +/* Add background picture */ +BODY { + background-image:url(background.gif); + background-attachment:fixed; +} + +.content UL.thumbnails SPAN.wrap2 { + height: 200px; /* max thumbnail height + 2px */ +} + diff --git a/BSF/tools/metadata.php b/BSF/tools/metadata.php new file mode 100644 index 000000000..871b32986 --- /dev/null +++ b/BSF/tools/metadata.php @@ -0,0 +1,97 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Piwigo - a PHP based picture gallery | +// +-----------------------------------------------------------------------+ +// | Copyright(C) 2008 Piwigo Team http://piwigo.org | +// | Copyright(C) 2003-2008 PhpWebGallery Team http://phpwebgallery.net | +// | Copyright(C) 2002-2003 Pierrick LE GALL http://le-gall.net/pierrick | +// +-----------------------------------------------------------------------+ +// | 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. | +// +-----------------------------------------------------------------------+ + +$filename = 'sample.jpg'; +echo 'Informations are read from '.$filename.'<br /><br /><br />'; + +/** + * return a cleaned IPTC value + * + * @param string value + * @return string + */ +function clean_iptc_value($value) +{ + // strip leading zeros (weird Kodak Scanner software) + while ( isset($value[0]) and $value[0] == chr(0)) + { + $value = substr($value, 1); + } + // remove binary nulls + $value = str_replace(chr(0x00), ' ', $value); + + return $value; +} + +$iptc_result = array(); +$imginfo = array(); +getimagesize($filename, $imginfo); +if (isset($imginfo['APP13'])) +{ + $iptc = iptcparse($imginfo['APP13']); + if (is_array($iptc)) + { + foreach (array_keys($iptc) as $iptc_key) + { + if (isset($iptc[$iptc_key][0])) + { + if ($iptc_key == '2#025') + { + $value = implode( + ',', + array_map( + 'clean_iptc_value', + $iptc[$iptc_key] + ) + ); + } + else + { + $value = clean_iptc_value($iptc[$iptc_key][0]); + } + + $iptc_result[$iptc_key] = $value; + } + } + } + + echo 'IPTC Fields in '.$filename.'<br />'; + $keys = array_keys($iptc_result); + sort($keys); + foreach ($keys as $key) + { + echo '<br />'.$key.' = '.$iptc_result[$key]; + } +} +else +{ + echo 'no IPTC information'; +} + +echo '<br /><br /><br />'; +echo 'EXIF Fields in '.$filename.'<br />'; +$exif = read_exif_data($filename); +echo '<pre>'; +print_r($exif); +echo '</pre>'; +?>
\ No newline at end of file diff --git a/BSF/tools/prototype.js b/BSF/tools/prototype.js new file mode 100644 index 000000000..c5a9ccc05 --- /dev/null +++ b/BSF/tools/prototype.js @@ -0,0 +1,3277 @@ +/* Prototype JavaScript framework, version 1.5.1.1 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.1.1', + + Browser: { + IE: !!(window.attachEvent && !window.opera), + Opera: !!window.opera, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + (document.createElement('div').__proto__ !== + document.createElement('form').__proto__) + }, + + ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + 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 (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(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; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch(type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (object.ownerDocument === document) return; + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (value !== undefined) + results.push(property.toJSON() + ': ' + value); + } + return '{' + results.join(', ') + '}'; + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +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, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +Date.prototype.toJSON = function() { + return '"' + this.getFullYear() + '-' + + (this.getMonth() + 1).toPaddedString(2) + '-' + + this.getDate().toPaddedString(2) + 'T' + + this.getHours().toPaddedString(2) + ':' + + this.getMinutes().toPaddedString(2) + ':' + + this.getSeconds().toPaddedString(2) + '"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < 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() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + 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(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (hash[key].constructor != Array) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + var result = ''; + for (var i = 0; i < count; i++) result += this; + return result; + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + }, + unescapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + 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 = false; + 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 || Prototype.K)(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; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + 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.map(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 (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + 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.map(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.map(); + }, + + 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) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + 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, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +if (Prototype.Browser.WebKit) { + $A = Array.from = function(iterable) { + if (!iterable) return []; + if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && + iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < 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 != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && 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, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (value !== undefined) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (arguments[i].constructor == Array) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(object) { + if (object instanceof Hash) this.merge(object); + else Object.extend(this, object || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + parts.add = arguments.callee.addPair; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + var value = pair.value; + + if (value && typeof value == 'object') { + if (value.constructor == Array) value.each(function(value) { + parts.add(pair.key, value); + }); + return; + } + parts.add(pair.key, value); + }); + + return parts.join('&'); + }, + + toJSON: function(object) { + var results = []; + this.prototype._each.call(object, function(pair) { + var value = Object.toJSON(pair.value); + if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); + }); + return '{' + results.join(', ') + '}'; + } +}); + +Hash.toQueryString.addPair = function(key, value, prefix) { + key = encodeURIComponent(key); + if (value === undefined) this.push(key); + else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); +} + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) 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(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + }, + + toJSON: function() { + return Hash.toJSON(this); + } +}); + +function $H(object) { + if (object instanceof Hash) return object; + return new Hash(object); +}; + +// Safari iterates over shadowed properties +if (function() { + var i = 0, Test = function(value) { this.key = value }; + Test.prototype.key = 'foo'; + for (var property in new Test('bar')) i++; + return i > 1; +}()) Hash.prototype._each = function(iterator) { + var cache = []; + for (var key in this) { + var value = this[key]; + if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; + cache.push(key); + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } +}; +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; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + 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 XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (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, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Hash.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + if (this.options.onCreate) this.options.onCreate(this.transport); + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + var contentType = this.getHeader('Content-type'); + if (contentType && contentType.strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? json.evalJSON() : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + 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.container = { + success: (container.success || 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, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + 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.options.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); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; + + document.getElementsByClassName = function(className, parentElement) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } + +} else document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + var elementClassName = child.className; + if (elementClassName.length == 0) continue; + if (elementClassName == className || elementClassName.match(pattern)) + elements.push(Element.extend(child)); + } + return elements; +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) var Element = {}; + +Element.extend = function(element) { + var F = Prototype.BrowserFeatures; + if (!element || !element.tagName || element.nodeType == 3 || + element._extended || F.SpecificElementExtensions || element == window) + return element; + + var methods = {}, tagName = element.tagName, cache = Element.extend.cache, + T = Element.Methods.ByTag; + + // extend methods for all tags (Safari doesn't need this) + if (!F.ElementExtensions) { + Object.extend(methods, Element.Methods), + Object.extend(methods, Element.Methods.Simulated); + } + + // extend methods for specific tags + if (T[tagName]) Object.extend(methods, T[tagName]); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + + element._extended = Prototype.emptyFunction; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')).each(Element.extend); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return expression ? Selector.findElement(ancestors, expression, index) : + ancestors[index || 0]; + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + var descendants = element.descendants(); + return expression ? Selector.findElement(descendants, expression, index) : + descendants[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return expression ? Selector.findElement(previousSiblings, expression, index) : + previousSiblings[index || 0]; + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return expression ? Selector.findElement(nextSiblings, expression, index) : + nextSiblings[index || 0]; + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + if (!element.attributes) return null; + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + return attribute ? attribute.nodeValue : null; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles, camelized) { + element = $(element); + var elementStyle = element.style; + + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]) + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : + (camelized ? property : property.camelize())] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + 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; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + 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; + } + } + return element; + }, + + 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 = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, { + childOf: Element.Methods.descendantOf, + childElements: Element.Methods.immediateDescendants +}); + +if (Prototype.Browser.Opera) { + Element.Methods._getStyle = Element.Methods.getStyle; + Element.Methods.getStyle = function(element, style) { + switch(style) { + case 'left': + case 'top': + case 'right': + case 'bottom': + if (Element._getStyle(element, 'position') == 'static') return null; + default: return Element._getStyle(element, style); + } + }; +} +else if (Prototype.Browser.IE) { + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset'+style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + element = $(element); + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); + return element; + } else if (value < 0.00001) value = 0; + style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + // IE is missing .innerHTML support for TABLE-related elements + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>'; + depth = 2; + break; + case 'TR': + div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>'; + depth = 3; + break; + case 'TD': + div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>'; + depth = 4; + } + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + depth.times(function() { div = div.firstChild }); + $A(div.childNodes).each(function(node) { element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() { html.evalScripts() }, 10); + return element; + } +} +else if (Prototype.Browser.Gecko) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +Element._attributeTranslations = { + names: { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } + } +}; + +(function() { + Object.extend(this, { + href: this._getAttr, + src: this._getAttr, + type: this._getAttr, + disabled: this._flag, + checked: this._flag, + readonly: this._flag, + multiple: this._flag + }); +}).call(Element._attributeTranslations.values); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations, node; + attribute = t.names[attribute] || attribute; + node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +Element.Methods.ByTag = {}; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div').__proto__) { + window.HTMLElement = {}; + window.HTMLElement.prototype = document.createElement('div').__proto__; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || {}); + else { + if (tagName.constructor == Array) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = {}; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = {}; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (typeof klass == "undefined") continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; +}; + +var 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) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + 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($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create(); + +Selector.prototype = { + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + compileMatcher: function() { + // Selectors with namespaced attributes can't use the XPath version + if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; return; + } + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(typeof c[i] == 'function' ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(typeof x[i] == 'function' ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + return this.findElements(document).include(element); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#<Selector:" + this.expression.inspect() + ">"; + } +}; + +Object.extend(Selector, { + _cache: {}, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: "[@#{1}]", + attr: function(m) { + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (typeof h === 'function') return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'checked': "[@checked]", + 'disabled': "[@disabled]", + 'enabled': "[not(@disabled)]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, m, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, + attrPresence: /^\[([\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._counted = true; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._counted) { + n._counted = true; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, children = [], child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + tagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() == tagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!nodes && root == document) return targetNode ? [targetNode] : []; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr) { + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._counted) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._counted) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv.startsWith(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + }, + + matchElements: function(elements, expression) { + var matches = new Selector(expression).findElements(), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._counted) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + var exprs = expressions.join(','), expressions = []; + exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != null) { + if (key in result) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || {}); + + var params = options.parameters; + options.parameters = form.serialize(true); + + if (params) { + if (typeof params == 'string') params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(form.readAttribute('action'), options); + } +} + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) {} + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +} + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +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(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + 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() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + 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, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + + 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, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (Prototype.Browser.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) { + if(element.tagName=='BODY') break; + var 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; + }, + + 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 { + if (!window.opera || element.tagName=='BODY') { + 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 (Prototype.Browser.WebKit) { + 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]; + } +} + +Element.addMethods();
\ No newline at end of file diff --git a/BSF/tools/pwg_rel_create.sh b/BSF/tools/pwg_rel_create.sh new file mode 100644 index 000000000..a2a584a61 --- /dev/null +++ b/BSF/tools/pwg_rel_create.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# +--------------------------------------------------------------------------+ +# | pwg_rel_create.sh | +# +--------------------------------------------------------------------------+ +# | author : Pierrick LE GALL <http://le-gall.net/pierrick> | +# | project : Piwigo | +# +--------------------------------------------------------------------------+ + +if [ $# -lt 2 ] +then + echo + echo 'usage : '$(basename $0)' <tag> <version number>' + echo + exit 1 +fi + +tag=$1 +version=$2 + +name=phpwebgallery-$version + +cd /tmp +if [ -e $name ] +then + rm -rf $name +fi + +if [ -e $version ] +then + rm -rf $version +fi +mkdir $version + +# cvs export -r $tag -d $version phpwebgallery +svn export http://svn.gna.org/svn/phpwebgallery/tags/$tag $name +# creating mysql.inc.php empty and writeable +touch $name/include/mysql.inc.php +chmod a+w $name/include/mysql.inc.php + +# find $name -name "*.php" \ +# | xargs grep -l 'branch 1.7' \ +# | xargs perl -pi -e "s/branch 1.7/${version}/g" + +cd /tmp +for ext in zip tar.gz tar.bz2 +do + file=$version/$name.$ext + if [ -f $file ] + then + rm $name + fi +done + + +zip -r $version/$name.zip $name +tar -czf $version/$name.tar.gz $name +tar -cjf $version/$name.tar.bz2 $name + +cd /tmp/$version +md5sum p* >MD5SUMS diff --git a/BSF/tools/release_creation.readme b/BSF/tools/release_creation.readme new file mode 100644 index 000000000..a8b4f6f52 --- /dev/null +++ b/BSF/tools/release_creation.readme @@ -0,0 +1,50 @@ +===================================== + Piwigo release creation guide +===================================== + +Technical creation +================== + +I take release 1.7.0 as an example. In URLs "plg" is my personnal Gna! +username, so use yours. + +- tag creation tags/release-1_7_0 : + +$ svn copy \ + -r 1999 \ + -m "Create release 1.7.0 from branch 1.7 r1999" \ + svn+ssh://plg@svn.gna.org/svn/phpwebgallery/branch/branch-1_7 \ + svn+ssh://plg@svn.gna.org/svn/phpwebgallery/tags/release-1_7_0 + +- checkout new Subversion release 1.7.0 + +$ svn co svn+ssh://plg@svn.gna.org/svn/phpwebgallery/tags/release-1_7_0 1.7.0 +$ cd 1.7.0 + +- in include/config_default.inc.php, change the following parameters: + - $conf['check_upgrade_feed'] = false; + - $conf['show_version'] = false; + - $conf['show_gt'] = false; + - $conf['die_on_sql_error'] = false; + +- in include/constants.php, change the PHPWG_VERSION to 1.7.0 + +- commit your changes to tags/release-1_7_0, with the following comment: + +> New version 1.7.0 hard coded. +> +> Stable release required modifications: don't show version and generation +> time in footer, don't check the upgrade feed, don't die on sql errors. + +$ svn commit + +- create the release (pwg_rel_create.sh is in tools directory) + +$ mkdir /tmp/1.7.0 +$ cd /tmp/1.7.0 +$ pwg_rel_create.sh release-1_7_0 1.7.0 +$ md5sum p* > MD5SUMS + +- upload the release to Gna! download area + +$ scp -r /tmp/1.7.0 plg@download.gna.org:/upload/phpwebgallery/release/1.7/ diff --git a/BSF/tools/ws.htm b/BSF/tools/ws.htm new file mode 100644 index 000000000..25eb21fe4 --- /dev/null +++ b/BSF/tools/ws.htm @@ -0,0 +1,402 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> +<head> +<title>PWG web service explorer</title> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> +<script type="text/javascript" src="prototype.js" ></script> + +<script type="text/javascript"> + +function setVisibility(id, vis) +{ + $(id).style.visibility = vis; +} + +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>'; + } + } + $("error").update(s); +} + +var gServiceUrl; +var gCachedMethods; + +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() +{ + $("error").update(""); + setVisibility("methodListWrapper", "hidden"); + $("methodList").update(""); + setVisibility("methodWrapper", "hidden"); + setVisibility("methodDetailWrapper", "hidden"); + + gServiceUrl = $F('ws_url'); + gCachedMethods = new Hash(); + + 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>'; + } + $("methodList").update(ml); + setVisibility("methodListWrapper", "visible"); +} + +function pwgSelectMethod(methodName) +{ + $("error").update(""); + $("methodName").update(methodName); + setVisibility("methodDetailWrapper", "hidden"); + setVisibility("methodWrapper", "visible"); + + if ( gCachedMethods[methodName] ) + fillNewMethod( gCachedMethods[methodName] ); + else + { + try { + var ajaxReq = new Ajax.Request( + gServiceUrl, + {method:'get', parameters:'format=json&method=reflection.getMethodDetails&methodName='+methodName, + onSuccess: function (r) { onSuccess_getMethodDetails(r); } + } + ) + }catch (e) + { + dumpError( e ); + } + } + return false; +} + +function onSuccess_getMethodDetails(transport) +{ + var result = pwgGetJsonResult(transport); + fillNewMethod( gCachedMethods[result.name] = $H(result) ); +} + +function fillNewMethod(method) +{ + var methodParamsElt = $("methodParams"); + while (methodParamsElt.tBodies[0].rows.length) + methodParamsElt.tBodies[0].deleteRow(methodParamsElt.tBodies[0].rows.length-1); + + if (method.params && method.params.length>0) + { + for (var i=0; i<method.params.length; i++) + { + var row = methodParamsElt.tBodies[0].insertRow(-1); + var isOptional = method.params[i].optional; + var acceptArray = method.params[i].acceptArray; + var defaultValue = method.params[i].defaultValue == null ? '' : method.params[i].defaultValue; + + row.insertCell(0).innerHTML = method.params[i].name; + row.insertCell(1).innerHTML = '<span title="parameter is '+(isOptional ? 'optional':'required') +'">'+(isOptional ? '?':'*')+'</span>' + + (method.params[i].acceptArray ? ' <span title="parameter can be an array; use | (pipe) character to split values">[ ]</span>':''); + row.insertCell(2).innerHTML = '<input id="methodParameterSend_'+i+'" type="checkbox" '+(isOptional ? '':'checked="checked"')+'/>'; + row.insertCell(3).innerHTML = '<input id="methodParameterValue_'+i+'"" value="'+defaultValue+'" style="width:99%" onchange="$(\'methodParameterSend_'+i+'\').checked=true;"/>'; + } + } + $("methodDescription").update(method.description); + setVisibility("methodDetailWrapper", "visible"); +} + +function pwgInvokeMethod( newWindow ) +{ + var methodName = $('methodName').innerHTML; + var method = gCachedMethods[methodName]; + + var reqUrl = gServiceUrl; + reqUrl += "?format="+$F('responseFormat'); + + if ($('requestFormat').value == 'get') + { + reqUrl += "&method="+methodName; + for ( var i=0; i<method.params.length; i++) + { + if (! $('methodParameterSend_'+i).checked) + continue; + + if ( method.params[i].acceptArray && $F('methodParameterValue_'+i).split('|').length > 1 ) + { + $F('methodParameterValue_'+i).split('|').each( + function(v) { + reqUrl += '&'+method.params[i].name+'[]='+v; + } + ); + } + else + reqUrl += '&'+method.params[i].name+'='+$F('methodParameterValue_'+i); + } + if ( !newWindow ) + $("invokeFrame").src = reqUrl; + else + window.open(reqUrl); + } + else + { + var form = $("invokeForm"); + form.action = reqUrl; + var t = '<input type="hidden" name="'+'method'+'" value="'+methodName+'"/>'; + for ( var i=0; i<method.params.length; i++) + { + if (! $('methodParameterSend_'+i).checked) + continue; + + if ( method.params[i].acceptArray && $F('methodParameterValue_'+i).split('|').length > 1 ) + { + $F('methodParameterValue_'+i).split('|').each( + function(v) { + t += '<input type="hidden" name="'+method.params[i].name+'[]" value="'+v+'"/>'; + } + ); + } + else + t += '<input type="hidden" name="'+method.params[i].name+'" value="'+$F('methodParameterValue_'+i)+'"/>'; + } + form.innerHTML = t; + form.target = newWindow ? "_blank" : "invokeFrame"; + form.submit(); + } + return false; +} +</script> + + +<style> +#methodListWrapper { + width: 13em; + float: left; + display: inline; + visibility: hidden; +} + +#methodList { + padding-left: 10px; + margin-left: 15px; +} + +#methodWrapper { + margin-left: 14em; + visibility: hidden; +} + +#methodName { + margin-top: 0; + margin-bottom: 3px; +} + + +#error { + height: 90px; + overflow: scroll; + color: red; +} + +#methodParams { + border-collapse: collapse; + font-size: small; +} + +#methodParams input { + font-size: 90%; + border: 1px solid black; + text-indent: 2px; +} + + +a { + color: #02f; + background-color: white; + text-decoration: underline; +} + +a:hover { + color: white; + background-color: #02f; + text-decoration: none; + cursor:pointer; +} + +</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"><h2>Methods</h2> + <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"> + + <table> + <tr style="vertical-align:top"> + + <td> + <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> + <p> + <a href="#" onclick="return pwgInvokeMethod(false)">Invoke</a> + <a href="#" onclick="return pwgInvokeMethod(true)">Invoke (new Window)</a> + </p> + </td> + + + <td> + <table id="methodParams" border="1" cellspacing="0" cellpadding="2px"> + <thead> + <tr> + <td style="width:150px">Parameter</td> + <td>Extra</td> + <td>Send</td> + <td style="width:160px">Value</td> + </tr> + </thead> + <tbody> + </tbody> + </table> + </td> + + </tr> + </table> + + <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" style="clear:both"></iframe> + </div> <!-- methodDetailWrapper --> +</div> <!-- methodWrapper --> + +</div> + +</body> +</html> |