diff options
Diffstat (limited to 'includes/pear')
402 files changed, 84321 insertions, 0 deletions
diff --git a/includes/pear/Archive/Tar.php b/includes/pear/Archive/Tar.php new file mode 100644 index 0000000..61eff98 --- /dev/null +++ b/includes/pear/Archive/Tar.php @@ -0,0 +1,1909 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * File::CSV + * + * PHP versions 4 and 5 + * + * Copyright (c) 1997-2008, + * Vincent Blavet <vincent@phpconcept.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet <vincent@phpconcept.net> + * @copyright 1997-2008 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Tar.php 295988 2010-03-09 08:39:37Z mrook $ + * @link http://pear.php.net/package/Archive_Tar + */ + +require_once 'PEAR.php'; + + +define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define ('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +/** +* Creates a (compressed) Tar archive +* +* @author Vincent Blavet <vincent@phpconcept.net> +* @version $Revision: 295988 $ +* @license http://www.opensource.org/licenses/bsd-license.php New BSD License +* @package Archive_Tar +*/ +class Archive_Tar extends PEAR +{ + /** + * @var string Name of the Tar + */ + var $_tarname=''; + + /** + * @var boolean if true, the Tar file will be gzipped + */ + var $_compress=false; + + /** + * @var string Type of compression : 'none', 'gz' or 'bz2' + */ + var $_compress_type='none'; + + /** + * @var string Explode separator + */ + var $_separator=' '; + + /** + * @var file descriptor + */ + var $_file=0; + + /** + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + var $_temp_tarname=''; + + /** + * @var string regular expression for ignoring files or directories + */ + var $_ignore_regexp=''; + + // {{{ constructor + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz' or 'bz2'. This + * parameter indicates if gzip or bz2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * @access public + */ + function Archive_Tar($p_tarname, $p_compress = null) + { + $this->PEAR(); + $this->_compress = false; + $this->_compress_type = 'none'; + if (($p_compress === null) || ($p_compress == '')) { + if (@file_exists($p_tarname)) { + if ($fp = @fopen($p_tarname, "rb")) { + // look for gzip magic cookie + $data = fread($fp, 2); + fclose($fp); + if ($data == "\37\213") { + $this->_compress = true; + $this->_compress_type = 'gz'; + // No sure it's enought for a magic code .... + } elseif ($data == "BZ") { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + // probably a remote file or some file accessible + // through a stream interface + if (substr($p_tarname, -2) == 'gz') { + $this->_compress = true; + $this->_compress_type = 'gz'; + } elseif ((substr($p_tarname, -3) == 'bz2') || + (substr($p_tarname, -2) == 'bz')) { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + if (($p_compress === true) || ($p_compress == 'gz')) { + $this->_compress = true; + $this->_compress_type = 'gz'; + } else if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + $this->_error("Unsupported compression type '$p_compress'\n". + "Supported types are 'gz' and 'bz2'.\n"); + return false; + } + } + $this->_tarname = $p_tarname; + if ($this->_compress) { // assert zlib or bz2 extension support + if ($this->_compress_type == 'gz') + $extname = 'zlib'; + else if ($this->_compress_type == 'bz2') + $extname = 'bz2'; + + if (!extension_loaded($extname)) { + PEAR::loadExtension($extname); + } + if (!extension_loaded($extname)) { + $this->_error("The extension '$extname' couldn't be found.\n". + "Please make sure your version of PHP was built ". + "with '$extname' support.\n"); + return false; + } + } + } + // }}} + + // {{{ destructor + function _Archive_Tar() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') + @unlink($this->_temp_tarname); + $this->_PEAR(); + } + // }}} + + // {{{ create() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function create($p_filelist) + { + return $this->createModify($p_filelist, '', ''); + } + // }}} + + // {{{ add() + /** + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function add($p_filelist) + { + return $this->addModify($p_filelist, '', ''); + } + // }}} + + // {{{ extract() + function extract($p_path='') + { + return $this->extractModify($p_path, ''); + } + // }}} + + // {{{ listContent() + function listContent() + { + $v_list_detail = array(); + + if ($this->_openRead()) { + if (!$this->_extractList('', $v_list_detail, "list", '', '')) { + unset($v_list_detail); + $v_list_detail = 0; + } + $this->_close(); + } + + return $v_list_detail; + } + // }}} + + // {{{ createModify() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * @return boolean true on success, false on error. + * @access public + * @see addModify() + */ + function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_openWrite()) + return false; + + if ($p_filelist != '') { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_cleanFile(); + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); + } + + if ($v_result) { + $this->_writeFooter(); + $this->_close(); + } else + $this->_cleanFile(); + + return $v_result; + } + // }}} + + // {{{ addModify() + /** + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * @return true on success, false on error. + * @access public + */ + function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_isArchive()) + $v_result = $this->createModify($p_filelist, $p_add_dir, + $p_remove_dir); + else { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); + } + + return $v_result; + } + // }}} + + // {{{ addString() + /** + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * @return true on success, false on error. + * @access public + */ + function addString($p_filename, $p_string) + { + $v_result = true; + + if (!$this->_isArchive()) { + if (!$this->_openWrite()) { + return false; + } + $this->_close(); + } + + if (!$this->_openAppend()) + return false; + + // Need to check the get back to the temporary file ? .... + $v_result = $this->_addString($p_filename, $p_string); + + $this->_writeFooter(); + + $this->_close(); + + return $v_result; + } + // }}} + + // {{{ extractModify() + /** + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @return boolean true on success, false on error. + * @access public + * @see extractList() + */ + function extractModify($p_path, $p_remove_path) + { + $v_result = true; + $v_list_detail = array(); + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, + "complete", 0, $p_remove_path); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * @param string $p_filename The path of the file to extract in a string. + * @return a string with the file content or NULL. + * @access public + */ + function extractInString($p_filename) + { + if ($this->_openRead()) { + $v_result = $this->_extractInString($p_filename); + $this->_close(); + } else { + $v_result = NULL; + } + + return $v_result; + } + // }}} + + // {{{ extractList() + /** + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @return true on success, false on error. + * @access public + * @see extractModify() + */ + function extractList($p_filelist, $p_path='', $p_remove_path='') + { + $v_result = true; + $v_list_detail = array(); + + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid string list'); + return false; + } + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, "partial", + $v_list, $p_remove_path); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ setAttribute() + /** + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * @param mixed $argv variable list of attributes and values + * @return true on success, false on error. + * @access public + */ + function setAttribute() + { + $v_result = true; + + // ----- Get the number of variable list of arguments + if (($v_size = func_num_args()) == 0) { + return true; + } + + // ----- Get the arguments + $v_att_list = &func_get_args(); + + // ----- Read the attributes + $i=0; + while ($i<$v_size) { + + // ----- Look for next option + switch ($v_att_list[$i]) { + // ----- Look for options that request a string value + case ARCHIVE_TAR_ATT_SEPARATOR : + // ----- Check the number of parameters + if (($i+1) >= $v_size) { + $this->_error('Invalid number of parameters for ' + .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + return false; + } + + // ----- Get the value + $this->_separator = $v_att_list[$i+1]; + $i++; + break; + + default : + $this->_error('Unknow attribute code '.$v_att_list[$i].''); + return false; + } + + // ----- Next attribute + $i++; + } + + return $v_result; + } + // }}} + + // {{{ setIgnoreRegexp() + /** + * This method sets the regular expression for ignoring files and directories + * at import, for example: + * $arch->setIgnoreRegexp("#CVS|\.svn#"); + * @param string $regexp regular expression defining which files or directories to ignore + * @access public + */ + function setIgnoreRegexp($regexp) + { + $this->_ignore_regexp = $regexp; + } + // }}} + + // {{{ setIgnoreList() + /** + * This method sets the regular expression for ignoring all files and directories + * matching the filenames in the array list at import, for example: + * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); + * @param array $list a list of file or directory names to ignore + * @access public + */ + function setIgnoreList($list) + { + $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $regexp = '#/'.join('$|/', $list).'#'; + $this->setIgnoreRegexp($regexp); + } + // }}} + + // {{{ _error() + function _error($p_message) + { + // ----- To be completed + $this->raiseError($p_message); + } + // }}} + + // {{{ _warning() + function _warning($p_message) + { + // ----- To be completed + $this->raiseError($p_message); + } + // }}} + + // {{{ _isArchive() + function _isArchive($p_filename=NULL) + { + if ($p_filename == NULL) { + $p_filename = $this->_tarname; + } + clearstatcache(); + return @is_file($p_filename) && !@is_link($p_filename); + } + // }}} + + // {{{ _openWrite() + function _openWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "wb9"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($this->_tarname, "w"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "wb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openRead() + function _openRead() + { + if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { + + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar').'.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error('Unable to open in read mode \'' + .$this->_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error('Unable to open in write mode \'' + .$this->_temp_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) + @fwrite($v_file_to, $v_data); + @fclose($v_file_from); + @fclose($v_file_to); + } + + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + + } else + // ----- File to open if the normal Tar file + $v_filename = $this->_tarname; + + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($v_filename, "rb"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($v_filename, "r"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($v_filename, "rb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read mode \''.$v_filename.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openReadWrite() + function _openReadWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "r+b"); + else if ($this->_compress_type == 'bz2') { + $this->_error('Unable to open bz2 in read/write mode \'' + .$this->_tarname.'\' (limitation of bz2 extension)'); + return false; + } else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "r+b"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read/write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _close() + function _close() + { + //if (isset($this->_file)) { + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + @gzclose($this->_file); + else if ($this->_compress_type == 'bz2') + @bzclose($this->_file); + else if ($this->_compress_type == 'none') + @fclose($this->_file); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + $this->_file = 0; + } + + // ----- Look if a local copy need to be erase + // Note that it might be interesting to keep the url for a time : ToDo + if ($this->_temp_tarname != '') { + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } + + return true; + } + // }}} + + // {{{ _cleanFile() + function _cleanFile() + { + $this->_close(); + + // ----- Look for a local copy + if ($this->_temp_tarname != '') { + // ----- Remove the local copy but not the remote tarname + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } else { + // ----- Remove the local tarname file + @unlink($this->_tarname); + } + $this->_tarname = ''; + + return true; + } + // }}} + + // {{{ _writeBlock() + function _writeBlock($p_binary_data, $p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data, $p_len); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + } + return true; + } + // }}} + + // {{{ _readBlock() + function _readBlock() + { + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + $v_block = @gzread($this->_file, 512); + else if ($this->_compress_type == 'bz2') + $v_block = @bzread($this->_file, 512); + else if ($this->_compress_type == 'none') + $v_block = @fread($this->_file, 512); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } + return $v_block; + } + // }}} + + // {{{ _jumpBlock() + function _jumpBlock($p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) + $p_len = 1; + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file)+($p_len*512)); + } + else if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i=0; $i<$p_len; $i++) + $this->_readBlock(); + } else if ($this->_compress_type == 'none') + @fseek($this->_file, $p_len*512, SEEK_CUR); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + return true; + } + // }}} + + // {{{ _writeFooter() + function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + // }}} + + // {{{ _addList() + function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result=true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) + return true; + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) + continue; + + if ($v_filename == '') + continue; + + // ----- ignore files and directories matching the ignore regular expression + if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) { + $this->_warning("File '$v_filename' ignored"); + continue; + } + + if (!file_exists($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) + return false; + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") + $p_temp_list[0] = $v_filename.'/'.$p_hitem; + else + $p_temp_list[0] = $p_hitem; + + $v_result = $this->_addList($p_temp_list, + $p_add_dir, + $p_remove_dir); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + // }}} + + // {{{ _addFile() + function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + $v_stored_filename = $p_filename; + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') + $p_remove_dir .= '/'; + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') + $v_stored_filename = $p_add_dir.$v_stored_filename; + else + $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); + + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning("Unable to open file '".$p_filename + ."' in binary read mode"); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + } + + return true; + } + // }}} + + // {{{ _addString() + function _addString($p_filename, $p_string) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + + if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), + time(), 384, "", 0, 0)) + return false; + + $i=0; + while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _writeHeader() + function _writeHeader($p_filename, $p_stored_filename) + { + if ($p_stored_filename == '') + $p_stored_filename = $p_filename; + $v_reduce_filename = $this->_pathReduction($p_stored_filename); + + if (strlen($v_reduce_filename) > 99) { + if (!$this->_writeLongHeader($v_reduce_filename)) + return false; + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); + + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); + + $v_linkname = ''; + + if (@is_link($p_filename)) { + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%011s", DecOct(0)); + } elseif (@is_dir($p_filename)) { + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); + } + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + $v_reduce_filename, $v_perms, $v_uid, + $v_gid, $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeHeaderBlock() + function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, + $p_type='', $p_uid=0, $p_gid=0) + { + $p_filename = $this->_pathReduction($p_filename); + + if (strlen($p_filename) > 99) { + if (!$this->_writeLongHeader($p_filename)) + return false; + } + + if ($p_type == "5") { + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_size = sprintf("%011s", DecOct($p_size)); + } + + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); + + $v_mtime = sprintf("%11s", DecOct($p_mtime)); + + $v_linkname = ''; + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + $p_filename, $v_perms, $v_uid, $v_gid, + $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $p_type, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeLongHeader() + function _writeLongHeader($p_filename) + { + $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); + + $v_typeflag = 'L'; + + $v_linkname = ''; + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + '././@LongLink', 0, 0, 0, $v_size, 0); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + // ----- Write the filename as content of the block + $i=0; + while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _readHeader() + function _readHeader($v_binary_data, &$v_header) + { + if (strlen($v_binary_data)==0) { + $v_header['filename'] = ''; + return true; + } + + if (strlen($v_binary_data) != 512) { + $v_header['filename'] = ''; + $this->_error('Invalid block size : '.strlen($v_binary_data)); + return false; + } + + if (!is_array($v_header)) { + $v_header = array(); + } + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156; $i<512; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + + $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" + ."a8checksum/a1typeflag/a100link/a6magic/a2version/" + ."a32uname/a32gname/a8devmajor/a8devminor", + $v_binary_data); + + // ----- Extract the checksum + $v_header['checksum'] = OctDec(trim($v_data['checksum'])); + if ($v_header['checksum'] != $v_checksum) { + $v_header['filename'] = ''; + + // ----- Look for last block (empty block) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + return true; + + $this->_error('Invalid checksum for file "'.$v_data['filename'] + .'" : '.$v_checksum.' calculated, ' + .$v_header['checksum'].' expected'); + return false; + } + + // ----- Extract the properties + $v_header['filename'] = $v_data['filename']; + if ($this->_maliciousFilename($v_header['filename'])) { + $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree'); + return false; + } + $v_header['mode'] = OctDec(trim($v_data['mode'])); + $v_header['uid'] = OctDec(trim($v_data['uid'])); + $v_header['gid'] = OctDec(trim($v_data['gid'])); + $v_header['size'] = OctDec(trim($v_data['size'])); + $v_header['mtime'] = OctDec(trim($v_data['mtime'])); + if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { + $v_header['size'] = 0; + } + $v_header['link'] = trim($v_data['link']); + /* ----- All these fields are removed form the header because + they do not carry interesting info + $v_header[magic] = trim($v_data[magic]); + $v_header[version] = trim($v_data[version]); + $v_header[uname] = trim($v_data[uname]); + $v_header[gname] = trim($v_data[gname]); + $v_header[devmajor] = trim($v_data[devmajor]); + $v_header[devminor] = trim($v_data[devminor]); + */ + + return true; + } + // }}} + + // {{{ _maliciousFilename() + /** + * Detect and report a malicious file name + * + * @param string $file + * @return bool + * @access private + */ + function _maliciousFilename($file) + { + if (strpos($file, '/../') !== false) { + return true; + } + if (strpos($file, '../') === 0) { + return true; + } + return false; + } + // }}} + + // {{{ _readLongHeader() + function _readLongHeader(&$v_header) + { + $v_filename = ''; + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= trim($v_content); + } + + // ----- Read the next header + $v_binary_data = $this->_readBlock(); + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + $v_filename = trim($v_filename); + $v_header['filename'] = $v_filename; + if ($this->_maliciousFilename($v_filename)) { + $this->_error('Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree'); + return false; + } + + return true; + } + // }}} + + // {{{ _extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * @param string $p_filename The path of the file to extract in a string. + * @return a string with the file content or NULL. + * @access private + */ + function _extractInString($p_filename) + { + $v_result_str = ""; + + While (strlen($v_binary_data = $this->_readBlock()) != 0) + { + if (!$this->_readHeader($v_binary_data, $v_header)) + return NULL; + + if ($v_header['filename'] == '') + continue; + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return NULL; + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error('Unable to extract in string a directory ' + .'entry {'.$v_header['filename'].'}'); + return NULL; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr($v_content, 0, + ($v_header['size'] % 512)); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } + + return NULL; + } + // }}} + + // {{{ _extractList() + function _extractList($p_path, &$p_list_detail, $p_mode, + $p_file_list, $p_remove_path) + { + $v_result=true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { + $p_path = "./".$p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) + $p_remove_path .= '/'; + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = TRUE; + $v_listing = FALSE; + break; + case "partial" : + $v_extract_all = FALSE; + $v_listing = FALSE; + break; + case "list" : + $v_extract_all = FALSE; + $v_listing = TRUE; + break; + default : + $this->_error('Invalid extract mode ('.$p_mode.')'); + return false; + } + + clearstatcache(); + + while (strlen($v_binary_data = $this->_readBlock()) != 0) + { + $v_extract_file = FALSE; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return false; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i=0; $i<sizeof($p_file_list); $i++) { + // ----- Look if it is a directory + if (substr($p_file_list[$i], -1) == '/') { + // ----- Look if the directory is in the filename path + if ((strlen($v_header['filename']) > strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i])) { + $v_extract_file = TRUE; + break; + } + } + + // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = TRUE; + break; + } + } + } else { + $v_extract_file = TRUE; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) + { + if (($p_remove_path != '') + && (substr($v_header['filename'], 0, $p_remove_path_size) + == $p_remove_path)) + $v_header['filename'] = substr($v_header['filename'], + $p_remove_path_size); + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') + $p_path = substr($p_path, 0, strlen($p_path)-1); + + if (substr($v_header['filename'], 0, 1) == '/') + $v_header['filename'] = $p_path.$v_header['filename']; + else + $v_header['filename'] = $p_path.'/'.$v_header['filename']; + } + if (file_exists($v_header['filename'])) { + if ( (@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '')) { + $this->_error('File '.$v_header['filename'] + .' already exists as a directory'); + return false; + } + if ( ($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5")) { + $this->_error('Directory '.$v_header['filename'] + .' already exists as a file'); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error('File '.$v_header['filename'] + .' already exists and is write protected'); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } + + // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck(($v_header['typeflag'] == "5" + ?$v_header['filename'] + :dirname($v_header['filename'])))) != 1) { + $this->_error('Unable to create path for '.$v_header['filename']); + return false; + } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error('Unable to create directory {' + .$v_header['filename'].'}'); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error('Unable to extract symbolic link {' + .$v_header['filename'].'}'); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error('Error while opening {'.$v_header['filename'] + .'} in write binary mode'); + return false; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (filesize($v_header['filename']) != $v_header['size']) { + $this->_error('Extracted file '.$v_header['filename'] + .' does not have the correct file size \'' + .filesize($v_header['filename']) + .'\' ('.$v_header['size'] + .' expected). Archive may be corrupted.'); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename']) + $v_file_dir = ''; + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) + $v_file_dir = '/'; + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } + } + + return true; + } + // }}} + + // {{{ _openAppend() + function _openAppend() + { + if (filesize($this->_tarname) == 0) + return $this->_openWrite(); + + if ($this->_compress) { + $this->_close(); + + if (!@rename($this->_tarname, $this->_tarname.".tmp")) { + $this->_error('Error while renaming \''.$this->_tarname + .'\' to temporary file \''.$this->_tarname + .'.tmp\''); + return false; + } + + if ($this->_compress_type == 'gz') + $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); + elseif ($this->_compress_type == 'bz2') + $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + + if ($v_temp_tar == 0) { + $this->_error('Unable to open file \''.$this->_tarname + .'.tmp\' in binary read mode'); + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if (!$this->_openWrite()) { + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if ($this->_compress_type == 'gz') { + while (!@gzeof($v_temp_tar)) { + $v_buffer = @gzread($v_temp_tar, 512); + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + // do not copy end blocks, we will re-make them + // after appending + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @gzclose($v_temp_tar); + } + elseif ($this->_compress_type == 'bz2') { + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @bzclose($v_temp_tar); + } + + if (!@unlink($this->_tarname.".tmp")) { + $this->_error('Error while deleting temporary file \'' + .$this->_tarname.'.tmp\''); + } + + } else { + // ----- For not compressed tar, just add files before the last + // one or two 512 bytes block + if (!$this->_openReadWrite()) + return false; + + clearstatcache(); + $v_size = filesize($this->_tarname); + + // We might have zero, one or two end blocks. + // The standard is two, but we should try to handle + // other cases. + fseek($this->_file, $v_size - 1024); + if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 1024); + } + elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 512); + } + } + + return true; + } + // }}} + + // {{{ _append() + function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + { + if (!$this->_openAppend()) + return false; + + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) + $this->_writeFooter(); + + $this->_close(); + + return true; + } + // }}} + + // {{{ _dirCheck() + + /** + * Check if a directory exists and create it (including parent + * dirs) if not. + * + * @param string $p_dir directory to check + * + * @return bool TRUE if the directory exists or was created + */ + function _dirCheck($p_dir) + { + clearstatcache(); + if ((@is_dir($p_dir)) || ($p_dir == '')) + return true; + + $p_parent_dir = dirname($p_dir); + + if (($p_parent_dir != $p_dir) && + ($p_parent_dir != '') && + (!$this->_dirCheck($p_parent_dir))) + return false; + + if (!@mkdir($p_dir, 0777)) { + $this->_error("Unable to create directory '$p_dir'"); + return false; + } + + return true; + } + + // }}} + + // {{{ _pathReduction() + + /** + * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", + * rand emove double slashes. + * + * @param string $p_dir path to reduce + * + * @return string reduced path + * + * @access private + * + */ + function _pathReduction($p_dir) + { + $v_result = ''; + + // ----- Look for not empty path + if ($p_dir != '') { + // ----- Explode path by directory names + $v_list = explode('/', $p_dir); + + // ----- Study directories from last to first + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } + else if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } + else if ( ($v_list[$i] == '') + && ($i!=(sizeof($v_list)-1)) + && ($i!=0)) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' + .$v_result:''); + } + } + } + $v_result = strtr($v_result, '\\', '/'); + return $v_result; + } + + // }}} + + // {{{ _translateWinPath() + function _translateWinPath($p_path, $p_remove_disk_letter=true) + { + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if ( ($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + // }}} + +} +?> diff --git a/includes/pear/Auth.php b/includes/pear/Auth.php new file mode 100644 index 0000000..f34b8f6 --- /dev/null +++ b/includes/pear/Auth.php @@ -0,0 +1,1330 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * The main include file for Auth package + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id$ + * @link http://pear.php.net/package/Auth + */ + +/** + * Returned if session exceeds idle time + */ +define('AUTH_IDLED', -1); +/** + * Returned if session has expired + */ +define('AUTH_EXPIRED', -2); +/** + * Returned if container is unable to authenticate user/password pair + */ +define('AUTH_WRONG_LOGIN', -3); +/** + * Returned if a container method is not supported. + */ +define('AUTH_METHOD_NOT_SUPPORTED', -4); +/** + * Returned if new Advanced security system detects a breach + */ +define('AUTH_SECURITY_BREACH', -5); +/** + * Returned if checkAuthCallback says session should not continue. + */ +define('AUTH_CALLBACK_ABORT', -6); + +/** + * Auth Log level - INFO + */ +define('AUTH_LOG_INFO', 6); +/** + * Auth Log level - DEBUG + */ +define('AUTH_LOG_DEBUG', 7); + +/** + * Auth Advanced Security - IP Checks + */ +define('AUTH_ADV_IPCHECK', 1); +/** + * Auth Advanced Security - User Agent Checks + */ +define('AUTH_ADV_USERAGENT', 2); +/** + * Auth Advanced Security - Challenge Response + */ +define('AUTH_ADV_CHALLENGE', 3); + + +/** + * PEAR::Auth + * + * The PEAR::Auth class provides methods for creating an + * authentication system using PHP. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision$ + * @link http://pear.php.net/package/Auth + */ +class Auth { + + // {{{ properties + + /** + * Auth lifetime in seconds + * + * If this variable is set to 0, auth never expires + * + * @var integer + * @see setExpire(), checkAuth() + */ + private $expire = 0; + + /** + * Has the auth session expired? + * + * @var bool + * @see checkAuth() + */ + private $expired = false; + + /** + * Maximum idletime in seconds + * + * The difference to $expire is, that the idletime gets + * refreshed each time checkAuth() is called. If this + * variable is set to 0, idletime is never checked. + * + * @var integer + * @see setIdle(), checkAuth() + */ + private $idle = 0; + + /** + * Is the maximum idletime over? + * + * @var boolean + * @see checkAuth() + */ + private $idled = false; + + /** + * Storage object + * + * @var object + * @see Auth(), validateLogin() + */ + private $storage = ''; + + /** + * User-defined function that creates the login screen + * + * @var string + */ + private $loginFunction = ''; + + /** + * Should the login form be displayed + * + * @var bool + * @see setShowlogin() + */ + private $showLogin = true; + + /** + * Is Login Allowed from this page + * + * @var bool + * @see setAllowLogin + */ + private $allowLogin = true; + + /** + * Current authentication status + * + * @var string + */ + private $status = ''; + + /** + * Username + * + * @var string + */ + private $username = ''; + + /** + * Password + * + * @var string + */ + private $password = ''; + + /** + * checkAuth callback function name + * + * @var string + * @see setCheckAuthCallback() + */ + private $checkAuthCallback = ''; + + /** + * Login callback function name + * + * @var string + * @see setLoginCallback() + */ + private $loginCallback = ''; + + /** + * Failed Login callback function name + * + * @var string + * @see setFailedLoginCallback() + */ + private $loginFailedCallback = ''; + + /** + * Logout callback function name + * + * @var string + * @see setLogoutCallback() + */ + private $logoutCallback = ''; + + /** + * Auth session-array name + * + * @var string + */ + private $_sessionName = '_authsession'; + + /** + * Package Version + * + * @var string + */ + private $version = "@version@"; + + /** + * Flag to use advanced security + * When set extra checks will be made to see if the + * user's IP or useragent have changed across requests. + * Turned off by default to preserve BC. + * + * @var mixed Boolean to turn all advanced security options on or off + * Array containing named values turning specific advanced + * security features on or off individually + * array( + * AUTH_ADV_IPCHECK => true, + * AUTH_ADV_USERAGENT => true, + * AUTH_ADV_CHALLENGE => true, + * ); + */ + private $advancedsecurity = false; + + /** + * Username key in POST array + * + * @var string + */ + private $_postUsername = 'username'; + + /** + * Password key in POST array + * + * @var string + */ + private $_postPassword = 'password'; + + /** + * Holds a reference to the session auth variable + * @var array + */ + private $session; + + /** + * Holds a reference to the global server variable + * @var array + */ + private $server; + + /** + * Holds a reference to the global post variable + * @var array + */ + private $post; + + /** + * Holds a reference to the global cookie variable + * @var array + */ + private $cookie; + + /** + * A hash to hold various superglobals as reference + * @var array + */ + private $authdata; + + /** + * How many times has checkAuth been called + * @var int + */ + private $authChecks = 0; + + /** + * PEAR::Log object + * + * @var object Log + */ + private $logger = null; + + /** + * Whether to enable logging of behaviour + * + * @var boolean + */ + private $enableLogging = false; + + /** + * Whether to regenerate session id everytime start is called + * + * @var boolean + */ + private $regenerateSessionId = false; + + // }}} + // {{{ Auth() [constructor] + + /** + * Constructor + * + * Set up the storage driver. + * + * @param string Type of the storage driver + * @param mixed Additional options for the storage driver + * (example: if you are using DB as the storage + * driver, you have to pass the dsn string here) + * + * @param string Name of the function that creates the login form + * @param boolean Should the login form be displayed if necessary? + * @return void + */ + function Auth($storageDriver, $options = '', $loginFunction = '', $showLogin = true) + { + $this->applyAuthOptions($options); + + // Start the session suppress error if already started + if(!session_id()){ + @session_start(); + if(!session_id()) { + // Throw error + include_once 'PEAR.php'; + PEAR::throwError('Session could not be started by Auth, ' + .'possibly headers are already sent, try putting ' + .'ob_start in the beginning of your script'); + } + } + + // Make Sure Auth session variable is there + if(!isset($_SESSION[$this->_sessionName])) { + $_SESSION[$this->_sessionName] = array(); + } + + // Assign Some globals to internal references, this will replace _importGlobalVariable + $this->session =& $_SESSION[$this->_sessionName]; + $this->server =& $_SERVER; + $this->post =& $_POST; + $this->cookie =& $_COOKIE; + + if ($loginFunction != '' && is_callable($loginFunction)) { + $this->loginFunction = $loginFunction; + } + + if (is_bool($showLogin)) { + $this->showLogin = $showLogin; + } + + if (is_object($storageDriver)) { + $this->storage =& $storageDriver; + // Pass a reference to auth to the container, ugly but works + // this is used by the DB container to use method setAuthData not staticaly. + $this->storage->_auth_obj =& $this; + } else { + // $this->storage = $this->_factory($storageDriver, $options); + // + $this->storage_driver = $storageDriver; + $this->storage_options =& $options; + } + } + + // }}} + // {{{ applyAuthOptions() + + /** + * Set the Auth options + * + * Some options which are Auth specific will be applied + * the rest will be left for usage by the container + * + * @param array An array of Auth options + * @return array The options which were not applied + */ + private function &applyAuthOptions(&$options) + { + if(is_array($options)){ + if (!empty($options['sessionName'])) { + $this->_sessionName = $options['sessionName']; + unset($options['sessionName']); + } + if (isset($options['allowLogin'])) { + $this->allowLogin = $options['allowLogin']; + unset($options['allowLogin']); + } + if (!empty($options['postUsername'])) { + $this->_postUsername = $options['postUsername']; + unset($options['postUsername']); + } + if (!empty($options['postPassword'])) { + $this->_postPassword = $options['postPassword']; + unset($options['postPassword']); + } + if (isset($options['advancedsecurity'])) { + $this->advancedsecurity = $options['advancedsecurity']; + unset($options['advancedsecurity']); + } + if (isset($options['enableLogging'])) { + $this->enableLogging = $options['enableLogging']; + unset($options['enableLogging']); + } + if (isset($options['regenerateSessionId']) && is_bool($options['regenerateSessionId'])) { + $this->regenerateSessionId = $options['regenerateSessionId']; + } + } + return($options); + } + + // }}} + // {{{ _loadStorage() + + /** + * Load Storage Driver if not already loaded + * + * Suspend storage instantiation to make Auth lighter to use + * for calls which do not require login + * + * @return bool True if the conainer is loaded, false if the container + * is already loaded + */ + private function _loadStorage() + { + if(!is_object($this->storage)) { + $this->storage =& $this->_factory($this->storage_driver, + $this->storage_options); + $this->storage->_auth_obj =& $this; + $this->log('Loaded storage container ('.$this->storage_driver.')', AUTH_LOG_DEBUG); + return(true); + } + return(false); + } + + // }}} + // {{{ _factory() + + /** + * Return a storage driver based on $driver and $options + * + * @static + * @param string $driver Type of storage class to return + * @param string $options Optional parameters for the storage class + * @return object Object Storage object + */ + private function &_factory($driver, $options = '') + { + $storage_class = 'Auth_Container_' . $driver; + include_once 'Auth/Container/' . $driver . '.php'; + $obj = new $storage_class($options); + return $obj; + } + + // }}} + // {{{ assignData() + + /** + * Assign data from login form to internal values + * + * This function takes the values for username and password + * from $HTTP_POST_VARS/$_POST and assigns them to internal variables. + * If you wish to use another source apart from $HTTP_POST_VARS/$_POST, + * you have to derive this function. + * + * @global $HTTP_POST_VARS, $_POST + * @see Auth + * @return void + */ + private function assignData() + { + $this->log('Auth::assignData() called.', AUTH_LOG_DEBUG); + + if ( isset($this->post[$this->_postUsername]) + && $this->post[$this->_postUsername] != '') { + $this->username = (get_magic_quotes_gpc() == 1 + ? stripslashes($this->post[$this->_postUsername]) + : $this->post[$this->_postUsername]); + } + if ( isset($this->post[$this->_postPassword]) + && $this->post[$this->_postPassword] != '') { + $this->password = (get_magic_quotes_gpc() == 1 + ? stripslashes($this->post[$this->_postPassword]) + : $this->post[$this->_postPassword] ); + } + } + + // }}} + // {{{ start() + + /** + * Start new auth session + * + * @return void + */ + public function start() + { + $this->log('Auth::start() called.', AUTH_LOG_DEBUG); + + // #10729 - Regenerate session id here if we are generating it on every + // page load. + if ($this->regenerateSessionId) { + session_regenerate_id(true); + } + + $this->assignData(); + if (!$this->checkAuth() && $this->allowLogin) { + $this->login(); + } + } + + // }}} + // {{{ login() + + /** + * Login function + * + * @return void + */ + private function login() + { + $this->log('Auth::login() called.', AUTH_LOG_DEBUG); + + $login_ok = false; + $this->_loadStorage(); + + // Check if using challenge response + (isset($this->post['authsecret']) && $this->post['authsecret'] == 1) + ? $usingChap = true + : $usingChap = false; + + + // When the user has already entered a username, we have to validate it. + if (!empty($this->username)) { + if (true === $this->storage->fetchData($this->username, $this->password, $usingChap)) { + $this->session['challengekey'] = md5($this->username.$this->password); + $login_ok = true; + $this->log('Successful login.', AUTH_LOG_INFO); + } + } + + if (!empty($this->username) && $login_ok) { + $this->setAuth($this->username); + if (is_callable($this->loginCallback)) { + $this->log('Calling loginCallback ('.$this->loginCallback.').', AUTH_LOG_DEBUG); + call_user_func_array($this->loginCallback, array($this->username, &$this)); + } + } + + // If the login failed or the user entered no username, + // output the login screen again. + if (!empty($this->username) && !$login_ok) { + $this->log('Incorrect login.', AUTH_LOG_INFO); + $this->status = AUTH_WRONG_LOGIN; + if (is_callable($this->loginFailedCallback)) { + $this->log('Calling loginFailedCallback ('.$this->loginFailedCallback.').', AUTH_LOG_DEBUG); + call_user_func_array($this->loginFailedCallback, array($this->username, &$this)); + } + } + + if ((empty($this->username) || !$login_ok) && $this->showLogin) { + $this->log('Rendering Login Form.', AUTH_LOG_INFO); + if (is_callable($this->loginFunction)) { + $this->log('Calling loginFunction ('.$this->loginFunction.').', AUTH_LOG_DEBUG); + call_user_func_array($this->loginFunction, array($this->username, $this->status, &$this)); + } else { + // BC fix Auth used to use drawLogin for this + // call is sub classes implement this + if (is_callable(array($this, 'drawLogin'))) { + $this->log('Calling Auth::drawLogin()', AUTH_LOG_DEBUG); + return $this->drawLogin($this->username, $this); + } + + $this->log('Using default Auth_Frontend_Html', AUTH_LOG_DEBUG); + + // New Login form + include_once 'Auth/Frontend/Html.php'; + return Auth_Frontend_Html::render($this, $this->username); + } + } else { + return; + } + } + + // }}} + // {{{ setExpire() + + /** + * Set the maximum expire time + * + * @param integer time in seconds + * @param bool add time to current expire time or not + * @return void + */ + public function setExpire($time, $add = false) + { + $add ? $this->expire += $time : $this->expire = $time; + } + + // }}} + // {{{ setIdle() + + /** + * Set the maximum idle time + * + * @param integer time in seconds + * @param bool add time to current maximum idle time or not + * @return void + */ + public function setIdle($time, $add = false) + { + $add ? $this->idle += $time : $this->idle = $time; + } + + // }}} + // {{{ setSessionName() + + /** + * Set name of the session to a customized value. + * + * If you are using multiple instances of PEAR::Auth + * on the same domain, you can change the name of + * session per application via this function. + * This will chnage the name of the session variable + * auth uses to store it's data in the session + * + * @param string New name for the session + * @return void + */ + public function setSessionName($name = 'session') + { + $this->_sessionName = '_auth_'.$name; + // Make Sure Auth session variable is there + if(!isset($_SESSION[$this->_sessionName])) { + $_SESSION[$this->_sessionName] = array(); + } + $this->session =& $_SESSION[$this->_sessionName]; + } + + // }}} + // {{{ setShowLogin() + + /** + * Should the login form be displayed if necessary? + * + * @param bool show login form or not + * @return void + */ + public function setShowLogin($showLogin = true) + { + $this->showLogin = $showLogin; + } + + // }}} + // {{{ setAllowLogin() + + /** + * Is Login Allowed from this page? + * + * @param bool allow login from this page or not + * @return void + */ + public function setAllowLogin($allowLogin = true) + { + $this->allowLogin = $allowLogin; + } + + // }}} + // {{{ setCheckAuthCallback() + + /** + * Register a callback function to be called whenever the validity of the login is checked + * The function will receive two parameters, the username and a reference to the auth object. + * + * @param string callback function name + * @return void + * @since Method available since Release 1.4.3 + */ + public function setCheckAuthCallback($checkAuthCallback) + { + $this->checkAuthCallback = $checkAuthCallback; + } + + // }}} + // {{{ setLoginCallback() + + /** + * Register a callback function to be called on user login. + * The function will receive two parameters, the username and a reference to the auth object. + * + * @param string callback function name + * @return void + * @see setLogoutCallback() + */ + public function setLoginCallback($loginCallback) + { + $this->loginCallback = $loginCallback; + } + + // }}} + // {{{ setFailedLoginCallback() + + /** + * Register a callback function to be called on failed user login. + * The function will receive two parameters, the username and a reference to the auth object. + * + * @param string callback function name + * @return void + */ + public function setFailedLoginCallback($loginFailedCallback) + { + $this->loginFailedCallback = $loginFailedCallback; + } + + // }}} + // {{{ setLogoutCallback() + + /** + * Register a callback function to be called on user logout. + * The function will receive three parameters, the username and a reference to the auth object. + * + * @param string callback function name + * @return void + * @see setLoginCallback() + */ + public function setLogoutCallback($logoutCallback) + { + $this->logoutCallback = $logoutCallback; + } + + // }}} + // {{{ setAuthData() + + /** + * Register additional information that is to be stored + * in the session. + * + * @param string Name of the data field + * @param mixed Value of the data field + * @param boolean Should existing data be overwritten? (default + * is true) + * @return void + */ + public function setAuthData($name, $value, $overwrite = true) + { + if (!empty($this->session['data'][$name]) && $overwrite == false) { + return; + } + $this->session['data'][$name] = $value; + } + + // }}} + // {{{ getAuthData() + + /** + * Get additional information that is stored in the session. + * + * If no value for the first parameter is passed, the method will + * return all data that is currently stored. + * + * @param string Name of the data field + * @return mixed Value of the data field. + */ + public function getAuthData($name = null) + { + if (!isset($this->session['data'])) { + return null; + } + if(!isset($name)) { + return $this->session['data']; + } + if (isset($name) && isset($this->session['data'][$name])) { + return $this->session['data'][$name]; + } + return null; + } + + // }}} + // {{{ setAuth() + + /** + * Register variable in a session telling that the user + * has logged in successfully + * + * @param string Username + * @return void + */ + public function setAuth($username) + { + $this->log('Auth::setAuth() called.', AUTH_LOG_DEBUG); + + // #10729 - Regenerate session id here only if generating at login only + // Don't do it if we are regenerating on every request so we don't + // regenerate it twice in one request. + if (!$this->regenerateSessionId) { + // #2021 - Change the session id to avoid session fixation attacks php 4.3.3 > + session_regenerate_id(true); + } + + if (!isset($this->session) || !is_array($this->session)) { + $this->session = array(); + } + + if (!isset($this->session['data'])) { + $this->session['data'] = array(); + } + + $this->session['sessionip'] = isset($this->server['REMOTE_ADDR']) + ? $this->server['REMOTE_ADDR'] + : ''; + $this->session['sessionuseragent'] = isset($this->server['HTTP_USER_AGENT']) + ? $this->server['HTTP_USER_AGENT'] + : ''; + $this->session['sessionforwardedfor'] = isset($this->server['HTTP_X_FORWARDED_FOR']) + ? $this->server['HTTP_X_FORWARDED_FOR'] + : ''; + + // This should be set by the container to something more safe + // Like md5(passwd.microtime) + if(empty($this->session['challengekey'])) { + $this->session['challengekey'] = md5($username.microtime()); + } + + $this->session['challengecookie'] = md5($this->session['challengekey'].microtime()); + setcookie('authchallenge', $this->session['challengecookie'], 0, '/'); + + $this->session['registered'] = true; + $this->session['username'] = $username; + $this->session['timestamp'] = time(); + $this->session['idle'] = time(); + } + + // }}} + // {{{ setAdvancedSecurity() + + /** + * Enables advanced security checks + * + * Currently only ip change and useragent change + * are detected + * @todo Add challenge cookies - Create a cookie which changes every time + * and contains some challenge key which the server can verify with + * a session var cookie might need to be crypted (user pass) + * @param bool Enable or disable + * @return void + */ + public function setAdvancedSecurity($flag=true) + { + $this->advancedsecurity = $flag; + } + + // }}} + // {{{ checkAuth() + + /** + * Checks if there is a session with valid auth information. + * + * @return boolean Whether or not the user is authenticated. + */ + public function checkAuth() + { + $this->log('Auth::checkAuth() called.', AUTH_LOG_DEBUG); + $this->authChecks++; + if (isset($this->session)) { + // Check if authentication session is expired + if ( $this->expire > 0 + && isset($this->session['timestamp']) + && ($this->session['timestamp'] + $this->expire) < time()) { + $this->log('Session Expired', AUTH_LOG_INFO); + $this->expired = true; + $this->status = AUTH_EXPIRED; + $this->logout(); + return false; + } + + // Check if maximum idle time is reached + if ( $this->idle > 0 + && isset($this->session['idle']) + && ($this->session['idle'] + $this->idle) < time()) { + $this->log('Session Idle Time Reached', AUTH_LOG_INFO); + $this->idled = true; + $this->status = AUTH_IDLED; + $this->logout(); + return false; + } + + if ( isset($this->session['registered']) + && isset($this->session['username']) + && $this->session['registered'] == true + && $this->session['username'] != '') { + Auth::updateIdle(); + + if ($this->_isAdvancedSecurityEnabled()) { + $this->log('Advanced Security Mode Enabled.', AUTH_LOG_DEBUG); + + // Only Generate the challenge once + if ( $this->authChecks == 1 + && $this->_isAdvancedSecurityEnabled(AUTH_ADV_CHALLENGE)) { + $this->log('Generating new Challenge Cookie.', AUTH_LOG_DEBUG); + $this->session['challengecookieold'] = $this->session['challengecookie']; + $this->session['challengecookie'] = md5($this->session['challengekey'].microtime()); + setcookie('authchallenge', $this->session['challengecookie'], 0, '/'); + } + + // Check for ip change + if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_IPCHECK) + && isset($this->server['REMOTE_ADDR']) + && $this->session['sessionip'] != $this->server['REMOTE_ADDR']) { + $this->log('Security Breach. Remote IP Address changed.', AUTH_LOG_INFO); + // Check if the IP of the user has changed, if so we + // assume a man in the middle attack and log him out + $this->expired = true; + $this->status = AUTH_SECURITY_BREACH; + $this->logout(); + return false; + } + + // Check for ip change (if connected via proxy) + if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_IPCHECK) + && isset($this->server['HTTP_X_FORWARDED_FOR']) + && $this->session['sessionforwardedfor'] != $this->server['HTTP_X_FORWARDED_FOR']) { + $this->log('Security Breach. Forwarded For IP Address changed.', AUTH_LOG_INFO); + // Check if the IP of the user connecting via proxy has + // changed, if so we assume a man in the middle attack + // and log him out. + $this->expired = true; + $this->status = AUTH_SECURITY_BREACH; + $this->logout(); + return false; + } + + // Check for useragent change + if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_USERAGENT) + && isset($this->server['HTTP_USER_AGENT']) + && $this->session['sessionuseragent'] != $this->server['HTTP_USER_AGENT']) { + $this->log('Security Breach. User Agent changed.', AUTH_LOG_INFO); + // Check if the User-Agent of the user has changed, if + // so we assume a man in the middle attack and log him out + $this->expired = true; + $this->status = AUTH_SECURITY_BREACH; + $this->logout(); + return false; + } + + // Check challenge cookie here, if challengecookieold is not set + // this is the first time and check is skipped + // TODO when user open two pages similtaneuly (open in new window,open + // in tab) auth breach is caused find out a way around that if possible + if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_CHALLENGE) + && isset($this->session['challengecookieold']) + && $this->session['challengecookieold'] != $this->cookie['authchallenge']) { + $this->log('Security Breach. Challenge Cookie mismatch.', AUTH_LOG_INFO); + $this->expired = true; + $this->status = AUTH_SECURITY_BREACH; + $this->logout(); + $this->login(); + return false; + } + } + + if (is_callable($this->checkAuthCallback)) { + $this->log('Calling checkAuthCallback ('.$this->checkAuthCallback.').', AUTH_LOG_DEBUG); + $checkCallback = call_user_func_array($this->checkAuthCallback, array($this->username, &$this)); + if ($checkCallback == false) { + $this->log('checkAuthCallback failed.', AUTH_LOG_INFO); + $this->expired = true; + $this->status = AUTH_CALLBACK_ABORT; + $this->logout(); + return false; + } + } + + $this->log('Session OK.', AUTH_LOG_INFO); + return true; + } + } else { + $this->log('Unable to locate session storage.', AUTH_LOG_DEBUG); + return false; + } + $this->log('No login session.', AUTH_LOG_DEBUG); + return false; + } + + // }}} + // {{{ staticCheckAuth() [static] + + /** + * Statically checks if there is a session with valid auth information. + * + * @access public + * @see checkAuth + * @return boolean Whether or not the user is authenticated. + */ + public static function staticCheckAuth($options = null) + { + static $staticAuth; + if(!isset($staticAuth)) { + $staticAuth = new Auth('null', $options); + } + $staticAuth->log('Auth::staticCheckAuth() called', AUTH_LOG_DEBUG); + return $staticAuth->checkAuth(); + } + + // }}} + // {{{ getAuth() + + /** + * Has the user been authenticated? + * + * Is there a valid login session. Previously this was different from + * checkAuth() but now it is just an alias. + * + * @return bool True if the user is logged in, otherwise false. + */ + public function getAuth() + { + $this->log('Auth::getAuth() called.', AUTH_LOG_DEBUG); + return $this->checkAuth(); + } + + // }}} + // {{{ logout() + + /** + * Logout function + * + * This function clears any auth tokens in the currently + * active session and executes the logout callback function, + * if any + * + * @return void + */ + public function logout() + { + $this->log('Auth::logout() called.', AUTH_LOG_DEBUG); + + if (is_callable($this->logoutCallback) && isset($this->session['username'])) { + $this->log('Calling logoutCallback ('.$this->logoutCallback.').', AUTH_LOG_DEBUG); + call_user_func_array($this->logoutCallback, array($this->session['username'], &$this)); + } + + $this->username = ''; + $this->password = ''; + + $this->session = null; + } + + // }}} + // {{{ updateIdle() + + /** + * Update the idletime + * + * @return void + */ + public function updateIdle() + { + $this->session['idle'] = time(); + } + + // }}} + // {{{ getUsername() + + /** + * Get the username + * + * @return string + */ + public function getUsername() + { + if (isset($this->session['username'])) { + return($this->session['username']); + } + return(''); + } + + // }}} + // {{{ getStatus() + + /** + * Get the current status + * + * @return string + */ + public function getStatus() + { + return $this->status; + } + + // }}} + // {{{ getPostUsernameField() + + /** + * Gets the post varible used for the username + * + * @return string + */ + public function getPostUsernameField() + { + return($this->_postUsername); + } + + // }}} + // {{{ getPostPasswordField() + + /** + * Gets the post varible used for the username + * + * @return string + */ + public function getPostPasswordField() + { + return($this->_postPassword); + } + + // }}} + // {{{ sessionValidThru() + + /** + * Returns the time up to the session is valid + * + * @return integer + */ + public function sessionValidThru() + { + if (!isset($this->session['idle'])) { + return 0; + } + if ($this->idle == 0) { + return 0; + } + return ($this->session['idle'] + $this->idle); + } + + // }}} + // {{{ listUsers() + + /** + * List all users that are currently available in the storage + * container + * + * @return array + */ + public function listUsers() + { + $this->log('Auth::listUsers() called.', AUTH_LOG_DEBUG); + $this->_loadStorage(); + return $this->storage->listUsers(); + } + + // }}} + // {{{ addUser() + + /** + * Add user to the storage container + * + * @param string Username + * @param string Password + * @param mixed Additional parameters + * @return mixed True on success, PEAR error object on error + * and AUTH_METHOD_NOT_SUPPORTED otherwise. + */ + public function addUser($username, $password, $additional = '') + { + $this->log('Auth::addUser() called.', AUTH_LOG_DEBUG); + $this->_loadStorage(); + return $this->storage->addUser($username, $password, $additional); + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @param string Username + * @return mixed True on success, PEAR error object on error + * and AUTH_METHOD_NOT_SUPPORTED otherwise. + */ + public function removeUser($username) + { + $this->log('Auth::removeUser() called.', AUTH_LOG_DEBUG); + $this->_loadStorage(); + return $this->storage->removeUser($username); + } + + // }}} + // {{{ changePassword() + + /** + * Change password for user in the storage container + * + * @param string Username + * @param string The new password + * @return mixed True on success, PEAR error object on error + * and AUTH_METHOD_NOT_SUPPORTED otherwise. + */ + public function changePassword($username, $password) + { + $this->log('Auth::changePassword() called', AUTH_LOG_DEBUG); + $this->_loadStorage(); + return $this->storage->changePassword($username, $password); + } + + // }}} + // {{{ log() + + /** + * Log a message from the Auth system + * + * @param string The message to log + * @param string The log level to log the message under. See the Log documentation for more info. + * @return boolean + */ + public function log($message, $level = AUTH_LOG_DEBUG) + { + if (!$this->enableLogging) return false; + + $this->_loadLogger(); + + $this->logger->log('AUTH: '.$message, $level); + } + + // }}} + // {{{ _loadLogger() + + /** + * Load Log object if not already loaded + * + * Suspend logger instantiation to make Auth lighter to use + * for calls which do not require logging + * + * @return bool True if the logger is loaded, false if the logger + * is already loaded + */ + private function _loadLogger() + { + if(is_null($this->logger)) { + if (!class_exists('Log')) { + include_once 'Log.php'; + } + $this->logger =& Log::singleton('null', + null, + 'auth['.getmypid().']', + array(), + AUTH_LOG_DEBUG); + return(true); + } + return(false); + } + + // }}} + // {{{ attachLogObserver() + + /** + * Attach an Observer to the Auth Log Source + * + * @param object Log_Observer A Log Observer instance + * @return boolean + */ + public function attachLogObserver(&$observer) { + + $this->_loadLogger(); + + return $this->logger->attach($observer); + + } + + // }}} + // {{{ _isAdvancedSecurityEnabled() + + /** + * Is advanced security enabled? + * + * Pass one of the Advanced Security constants as the first parameter + * to check if that advanced security check is enabled. + * + * @param integer + * @return boolean + */ + private function _isAdvancedSecurityEnabled($feature = null) { + + if (is_null($feature)) { + + if ($this->advancedsecurity === true) + return true; + + if ( is_array($this->advancedsecurity) + && in_array(true, $this->advancedsecurity, true)) + return true; + + return false; + + } else { + + if (is_array($this->advancedsecurity)) { + + if ( isset($this->advancedsecurity[$feature]) + && $this->advancedsecurity[$feature] == true) + return true; + + return false; + + } + + return (bool)$this->advancedsecurity; + + } + + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Anonymous.php b/includes/pear/Auth/Anonymous.php new file mode 100644 index 0000000..ec29bd6 --- /dev/null +++ b/includes/pear/Auth/Anonymous.php @@ -0,0 +1,138 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Anonymous authentication support + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Yavor Shahpasov <yavo@netsmart.com.cy> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Anonymous.php 289651 2009-10-15 04:39:07Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.3.0 + */ + +/** + * Include Auth package + */ +require_once 'Auth.php'; + +/** + * Anonymous Authentication + * + * This class provides anonymous authentication if username and password + * were not supplied + * + * @category Authentication + * @package Auth + * @author Yavor Shahpasov <yavo@netsmart.com.cy> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 289651 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.3.0 + */ +class Auth_Anonymous extends Auth +{ + + // {{{ properties + + /** + * Whether to allow anonymous authentication + * + * @var boolean + */ + var $allow_anonymous = true; + + /** + * Username to use for anonymous user + * + * @var string + */ + var $anonymous_username = 'anonymous'; + + // }}} + // {{{ Auth_Anonymous() [constructor] + + /** + * Pass all parameters to Parent Auth class + * + * Set up the storage driver. + * + * @param string Type of the storage driver + * @param mixed Additional options for the storage driver + * (example: if you are using DB as the storage + * driver, you have to pass the dsn string here) + * + * @param string Name of the function that creates the login form + * @param boolean Should the login form be displayed if necessary? + * @return void + * @see Auth::Auth() + */ + function Auth_Anonymous($storageDriver, $options = '', $loginFunction = '', $showLogin = true) { + parent::Auth($storageDriver, $options, $loginFunction, $showLogin); + } + + // }}} + // {{{ login() + + /** + * Login function + * + * If no username & password is passed then login as the username + * provided in $this->anonymous_username else call standard login() + * function. + * + * @return void + * @access private + * @see Auth::login() + */ + function login() { + if ( $this->allow_anonymous + && empty($this->username) + && empty($this->password) ) { + $this->setAuth($this->anonymous_username); + if (is_callable($this->loginCallback)) { + call_user_func_array($this->loginCallback, array($this->username, $this) ); + } + } else { + // Call normal login system + parent::login(); + } + } + + // }}} + // {{{ forceLogin() + + /** + * Force the user to login + * + * Calling this function forces the user to provide a real username and + * password before continuing. + * + * @return void + */ + function forceLogin() { + $this->allow_anonymous = false; + if( !empty($this->session['username']) && $this->session['username'] == $this->anonymous_username ) { + $this->logout(); + } + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Auth.php b/includes/pear/Auth/Auth.php new file mode 100644 index 0000000..61ffe5c --- /dev/null +++ b/includes/pear/Auth/Auth.php @@ -0,0 +1,30 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Provide compatibility with previous Auth include location. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Auth.php 208437 2006-03-02 06:53:08Z aashley $ + * @link http://pear.php.net/package/Auth + * @deprecated File deprecated since Release 1.2.0 + */ + +/** + * Include Auth package + */ +require_once 'Auth.php'; + +?> diff --git a/includes/pear/Auth/Container.php b/includes/pear/Auth/Container.php new file mode 100644 index 0000000..5724d7d --- /dev/null +++ b/includes/pear/Auth/Container.php @@ -0,0 +1,262 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Auth_Container Base Class + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Container.php 294935 2010-02-12 00:05:45Z clockwerx $ + * @link http://pear.php.net/package/Auth + */ + +/** + * Storage class for fetching login data + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 294935 $ + * @link http://pear.php.net/package/Auth + */ +class Auth_Container +{ + + // {{{ properties + + /** + * User that is currently selected from the storage container. + * + * @access public + */ + var $activeUser = ""; + + /** + * The Auth object this container is attached to. + * + * @access public + */ + var $_auth_obj = null; + + // }}} + // {{{ Auth_Container() [constructor] + + /** + * Constructor + * + * Has to be overwritten by each storage class + * + * @access public + */ + function Auth_Container() + { + } + + // }}} + // {{{ fetchData() + + /** + * Fetch data from storage container + * + * Has to be overwritten by each storage class + * + * @access public + */ + function fetchData($username, $password, $isChallengeResponse=false) + { + $this->log('Auth_Container::fetchData() called.', AUTH_LOG_DEBUG); + } + + // }}} + // {{{ verifyPassword() + + /** + * Crypt and verfiy the entered password + * + * @param string Entered password + * @param string Password from the data container (usually this password + * is already encrypted. + * @param string Type of algorithm with which the password from + * the container has been crypted. (md5, crypt etc.) + * Defaults to "md5". + * @return bool True, if the passwords match + */ + function verifyPassword($password1, $password2, $cryptType = "md5") + { + $this->log('Auth_Container::verifyPassword() called.', AUTH_LOG_DEBUG); + switch ($cryptType) { + case "crypt" : + return ((string)crypt($password1, $password2) === (string)$password2); + break; + case "none" : + case "" : + return ((string)$password1 === (string)$password2); + break; + case "md5" : + return ((string)md5($password1) === (string)$password2); + break; + default : + if (function_exists($cryptType)) { + return ((string)$cryptType($password1) === (string)$password2); + } elseif (method_exists($this,$cryptType)) { + return ((string)$this->$cryptType($password1) === (string)$password2); + } else { + return false; + } + break; + } + } + + // }}} + // {{{ supportsChallengeResponse() + + /** + * Returns true if the container supports Challenge Response + * password authentication + */ + function supportsChallengeResponse() + { + return(false); + } + + // }}} + // {{{ getCryptType() + + /** + * Returns the crypt current crypt type of the container + * + * @return string + */ + function getCryptType() + { + return(''); + } + + // }}} + // {{{ listUsers() + + /** + * List all users that are available from the storage container + */ + function listUsers() + { + $this->log('Auth_Container::listUsers() called.', AUTH_LOG_DEBUG); + return AUTH_METHOD_NOT_SUPPORTED; + } + + // }}} + // {{{ getUser() + + /** + * Returns a user assoc array + * + * Containers which want should overide this + * + * @param string The username + */ + function getUser($username) + { + $this->log('Auth_Container::getUser() called.', AUTH_LOG_DEBUG); + $users = $this->listUsers(); + if ($users === AUTH_METHOD_NOT_SUPPORTED) { + return AUTH_METHOD_NOT_SUPPORTED; + } + for ($i=0; $c = count($users), $i<$c; $i++) { + if ($users[$i]['username'] == $username) { + return $users[$i]; + } + } + return false; + } + + // }}} + // {{{ addUser() + + /** + * Add a new user to the storage container + * + * @param string Username + * @param string Password + * @param array Additional information + * + * @return boolean + */ + function addUser($username, $password, $additional=null) + { + $this->log('Auth_Container::addUser() called.', AUTH_LOG_DEBUG); + return AUTH_METHOD_NOT_SUPPORTED; + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @param string Username + */ + function removeUser($username) + { + $this->log('Auth_Container::removeUser() called.', AUTH_LOG_DEBUG); + return AUTH_METHOD_NOT_SUPPORTED; + } + + // }}} + // {{{ changePassword() + + /** + * Change password for user in the storage container + * + * @param string Username + * @param string The new password + */ + function changePassword($username, $password) + { + $this->log('Auth_Container::changePassword() called.', AUTH_LOG_DEBUG); + return AUTH_METHOD_NOT_SUPPORTED; + } + + // }}} + // {{{ log() + + /** + * Log a message to the Auth log + * + * @param string The message + * @param int + * @return boolean + */ + function log($message, $level = AUTH_LOG_DEBUG) { + + if (is_null($this->_auth_obj)) { + + return false; + + } else { + + return $this->_auth_obj->log($message, $level); + + } + + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Container/Array.php b/includes/pear/Auth/Container/Array.php new file mode 100644 index 0000000..334080c --- /dev/null +++ b/includes/pear/Auth/Container/Array.php @@ -0,0 +1,161 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against a PHP Array + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author georg_1 at have2 dot com + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Array.php 237449 2007-06-12 03:11:27Z aashley $ + * @since File available since Release 1.4.0 + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR package for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for fetching authentication data from a PHP Array + * + * This container takes two options when configuring: + * + * cryptType: The crypt used to store the password. Currently recognised + * are: none, md5 and crypt. default: none + * users: A named array of usernames and passwords. + * Ex: + * array( + * 'guest' => '084e0343a0486ff05530df6c705c8bb4', // password guest + * 'georg' => 'fc77dba827fcc88e0243404572c51325' // password georg + * ) + * + * Usage Example: + * <?php + * $AuthOptions = array( + * 'users' => array( + * 'guest' => '084e0343a0486ff05530df6c705c8bb4', // password guest + * 'georg' => 'fc77dba827fcc88e0243404572c51325' // password georg + * ), + * 'cryptType'=>'md5', + * ); + * + * $auth = new Auth("Array", $AuthOptions); + * ?> + * + * @category Authentication + * @package Auth + * @author georg_1 at have2 dot com + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @since File available since Release 1.4.0 + */ + +class Auth_Container_Array extends Auth_Container { + + // {{{ properties + + /** + * The users and their password to authenticate against + * + * @var array $users + */ + var $users; + + /** + * The cryptType used on the passwords + * + * @var string $cryptType + */ + var $cryptType = 'none'; + + // }}} + // {{{ Auth_Container_Array() + + /** + * Constructor for Array Container + * + * @param array $data Options for the container + * @return void + */ + function Auth_Container_Array($data) + { + if (!is_array($data)) { + PEAR::raiseError('The options for Auth_Container_Array must be an array'); + } + if (isset($data['users']) && is_array($data['users'])) { + $this->users = $data['users']; + } else { + $this->users = array(); + PEAR::raiseError('Auth_Container_Array: no user data found in options array'); + } + if (isset($data['cryptType'])) { + $this->cryptType = $data['cryptType']; + } + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from array + * + * This function uses the given username to fetch the corresponding + * login data from the array. If an account that matches the passed + * username and password is found, the function returns true. + * Otherwise it returns false. + * + * @param string Username + * @param string Password + * @return boolean|PEAR_Error Error object or boolean + */ + function fetchData($user, $pass) + { + $this->log('Auth_Container_Array::fetchData() called.', AUTH_LOG_DEBUG); + if ( isset($this->users[$user]) + && $this->verifyPassword($pass, $this->users[$user], $this->cryptType)) { + return true; + } + return false; + } + + // }}} + // {{{ listUsers() + + /** + * Returns a list of users available within the container + * + * @return array + */ + function listUsers() + { + $this->log('Auth_Container_Array::listUsers() called.', AUTH_LOG_DEBUG); + $ret = array(); + foreach ($this->users as $username => $password) { + $ret[]['username'] = $username; + } + return $ret; + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Container/DB.php b/includes/pear/Auth/Container/DB.php new file mode 100644 index 0000000..9137d39 --- /dev/null +++ b/includes/pear/Auth/Container/DB.php @@ -0,0 +1,639 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against PEAR DB + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: DB.php 256753 2008-04-04 07:57:02Z aashley $ + * @link http://pear.php.net/package/Auth + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR DB + */ +require_once 'DB.php'; + +/** + * Storage driver for fetching login data from a database + * + * This storage driver can use all databases which are supported + * by the PEAR DB abstraction layer to fetch login data. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 256753 $ + * @link http://pear.php.net/package/Auth + */ +class Auth_Container_DB extends Auth_Container +{ + + // {{{ properties + + /** + * Additional options for the storage container + * @var array + */ + var $options = array(); + + /** + * DB object + * @var object + */ + var $db = null; + var $dsn = ''; + + /** + * User that is currently selected from the DB. + * @var string + */ + var $activeUser = ''; + + // }}} + // {{{ Auth_Container_DB [constructor] + + /** + * Constructor of the container class + * + * Save the initial options passed to the container. Initiation of the DB + * connection is no longer performed here and is only done when needed. + * + * @param string Connection data or DB object + * @return object Returns an error object if something went wrong + */ + function Auth_Container_DB($dsn) + { + $this->_setDefaults(); + + if (is_array($dsn)) { + $this->_parseOptions($dsn); + + if (empty($this->options['dsn'])) { + PEAR::raiseError('No connection parameters specified!'); + } + } else { + $this->options['dsn'] = $dsn; + } + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @access private + * @param string DSN string + * @return mixed Object on error, otherwise bool + */ + function _connect($dsn) + { + $this->log('Auth_Container_DB::_connect() called.', AUTH_LOG_DEBUG); + + if (is_string($dsn) || is_array($dsn)) { + $this->db = DB::Connect($dsn, $this->options['db_options']); + } elseif (is_subclass_of($dsn, 'db_common')) { + $this->db = $dsn; + } elseif (DB::isError($dsn)) { + return PEAR::raiseError($dsn->getMessage(), $dsn->getCode()); + } else { + return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + } + + if (DB::isError($this->db) || PEAR::isError($this->db)) { + return PEAR::raiseError($this->db->getMessage(), $this->db->getCode()); + } else { + return true; + } + } + + // }}} + // {{{ _prepare() + + /** + * Prepare database connection + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * + * @access private + * @return mixed True or a DB error object. + */ + function _prepare() + { + if (!DB::isConnection($this->db)) { + $res = $this->_connect($this->options['dsn']); + if (DB::isError($res) || PEAR::isError($res)) { + return $res; + } + } + if ($this->options['auto_quote'] && $this->db->dsn['phptype'] != 'sqlite') { + if (strpos('.', $this->options['table']) === false) { + $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']); + } else { + $t = explode('.', $this->options['table']); + for ($i = 0, $count = count($t); $i < $count; $i++) + $t[$i] = $this->db->quoteIdentifier($t[$i]); + $this->options['final_table'] = implode('.', $t); + } + $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']); + $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']); + } else { + $this->options['final_table'] = $this->options['table']; + $this->options['final_usernamecol'] = $this->options['usernamecol']; + $this->options['final_passwordcol'] = $this->options['passwordcol']; + } + return true; + } + + // }}} + // {{{ query() + + /** + * Prepare query to the database + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * After that the query is passed to the database. + * + * @access public + * @param string Query string + * @return mixed a DB_result object or DB_OK on success, a DB + * or PEAR error on failure + */ + function query($query) + { + $err = $this->_prepare(); + if ($err !== true) { + return $err; + } + return $this->db->query($query); + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['table'] = 'auth'; + $this->options['usernamecol'] = 'username'; + $this->options['passwordcol'] = 'password'; + $this->options['dsn'] = ''; + $this->options['db_fields'] = ''; + $this->options['cryptType'] = 'md5'; + $this->options['db_options'] = array(); + $this->options['db_where'] = ''; + $this->options['auto_quote'] = true; + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + foreach ($array as $key => $value) { + if (isset($this->options[$key])) { + $this->options[$key] = $value; + } + } + } + + // }}} + // {{{ _quoteDBFields() + + /** + * Quote the db_fields option to avoid the possibility of SQL injection. + * + * @access private + * @return string A properly quoted string that can be concatenated into a + * SELECT clause. + */ + function _quoteDBFields() + { + if (isset($this->options['db_fields'])) { + if (is_array($this->options['db_fields'])) { + if ($this->options['auto_quote']) { + $fields = array(); + foreach ($this->options['db_fields'] as $field) { + $fields[] = $this->db->quoteIdentifier($field); + } + return implode(', ', $fields); + } else { + return implode(', ', $this->options['db_fields']); + } + } else { + if (strlen($this->options['db_fields']) > 0) { + if ($this->options['auto_quote']) { + return $this->db->quoteIdentifier($this->options['db_fields']); + } else { + return $this->options['db_fields']; + } + } + } + } + + return ''; + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from database + * + * This function uses the given username to fetch + * the corresponding login data from the database + * table. If an account that matches the passed username + * and password is found, the function returns true. + * Otherwise it returns false. + * + * @param string Username + * @param string Password + * @param boolean If true password is secured using a md5 hash + * the frontend and auth are responsible for making sure the container supports + * challenge response password authentication + * @return mixed Error object or boolean + */ + function fetchData($username, $password, $isChallengeResponse=false) + { + $this->log('Auth_Container_DB::fetchData() called.', AUTH_LOG_DEBUG); + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + // Find if db_fields contains a *, if so assume all columns are selected + if (is_string($this->options['db_fields']) + && strstr($this->options['db_fields'], '*')) { + $sql_from = "*"; + } else { + $sql_from = $this->options['final_usernamecol']. + ", ".$this->options['final_passwordcol']; + + if (strlen($fields = $this->_quoteDBFields()) > 0) { + $sql_from .= ', '.$fields; + } + } + + $query = "SELECT ".$sql_from. + " FROM ".$this->options['final_table']. + " WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } + + if (!is_array($res)) { + $this->activeUser = ''; + return false; + } + + // Perform trimming here before the hashihg + $password = trim($password, "\r\n"); + $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n"); + + // If using Challenge Response md5 the pass with the secret + if ($isChallengeResponse) { + $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']] + .$this->_auth_obj->session['loginchallenege']); + + // UGLY cannot avoid without modifying verifyPassword + if ($this->options['cryptType'] == 'md5') { + $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]); + } + + //print " Hashed Password [{$res[$this->options['passwordcol']]}]<br/>\n"; + } + + if ($this->verifyPassword($password, + $res[$this->options['passwordcol']], + $this->options['cryptType'])) { + // Store additional field values in the session + foreach ($res as $key => $value) { + if ($key == $this->options['passwordcol'] || + $key == $this->options['usernamecol']) { + continue; + } + + $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG); + + // Use reference to the auth object if exists + // This is because the auth session variable can change so a + // static call to setAuthData does not make sence + $this->_auth_obj->setAuthData($key, $value); + } + return true; + } + $this->activeUser = $res[$this->options['usernamecol']]; + return false; + } + + // }}} + // {{{ listUsers() + + /** + * Returns a list of users from the container + * + * @return mixed + * @access public + */ + function listUsers() + { + $this->log('Auth_Container_DB::listUsers() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + $retVal = array(); + + // Find if db_fields contains a *, if so assume all col are selected + if ( is_string($this->options['db_fields']) + && strstr($this->options['db_fields'], '*')) { + $sql_from = "*"; + } else { + $sql_from = $this->options['final_usernamecol']. + ", ".$this->options['final_passwordcol']; + + if (strlen($fields = $this->_quoteDBFields()) > 0) { + $sql_from .= ', '.$fields; + } + } + + $query = sprintf("SELECT %s FROM %s", + $sql_from, + $this->options['final_table'] + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " WHERE ".$this->options['db_where']; + } + + $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + foreach ($res as $user) { + $user['username'] = $user[$this->options['usernamecol']]; + $retVal[] = $user; + } + } + $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG); + return $retVal; + } + + // }}} + // {{{ addUser() + + /** + * Add user to the storage container + * + * @access public + * @param string Username + * @param string Password + * @param mixed Additional information that are stored in the DB + * + * @return mixed True on success, otherwise error object + */ + function addUser($username, $password, $additional = "") + { + $this->log('Auth_Container_DB::addUser() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + if ( isset($this->options['cryptType']) + && $this->options['cryptType'] == 'none') { + $cryptFunction = 'strval'; + } elseif ( isset($this->options['cryptType']) + && function_exists($this->options['cryptType'])) { + $cryptFunction = $this->options['cryptType']; + } else { + $cryptFunction = 'md5'; + } + + $password = $cryptFunction($password); + + $additional_key = ''; + $additional_value = ''; + + if (is_array($additional)) { + foreach ($additional as $key => $value) { + if ($this->options['auto_quote']) { + $additional_key .= ', ' . $this->db->quoteIdentifier($key); + } else { + $additional_key .= ', ' . $key; + } + $additional_value .= ", " . $this->db->quoteSmart($value); + } + } + + $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)", + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->options['final_passwordcol'], + $additional_key, + $this->db->quoteSmart($username), + $this->db->quoteSmart($password), + $additional_value + ); + + $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + return true; + } + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @access public + * @param string Username + * + * @return mixed True on success, otherwise error object + */ + function removeUser($username) + { + $this->log('Auth_Container_DB::removeUser() called.', AUTH_LOG_DEBUG); + + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $where = " AND ".$this->options['db_where']; + } else { + $where = ''; + } + + $query = sprintf("DELETE FROM %s WHERE %s = %s %s", + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->db->quoteSmart($username), + $where + ); + + $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + return true; + } + } + + // }}} + // {{{ changePassword() + + /** + * Change password for user in the storage container + * + * @param string Username + * @param string The new password (plain text) + */ + function changePassword($username, $password) + { + $this->log('Auth_Container_DB::changePassword() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + if ( isset($this->options['cryptType']) + && $this->options['cryptType'] == 'none') { + $cryptFunction = 'strval'; + } elseif ( isset($this->options['cryptType']) + && function_exists($this->options['cryptType'])) { + $cryptFunction = $this->options['cryptType']; + } else { + $cryptFunction = 'md5'; + } + + $password = $cryptFunction($password); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $where = " AND ".$this->options['db_where']; + } else { + $where = ''; + } + + $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s %s", + $this->options['final_table'], + $this->options['final_passwordcol'], + $this->db->quoteSmart($password), + $this->options['final_usernamecol'], + $this->db->quoteSmart($username), + $where + ); + + $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + return true; + } + } + + // }}} + // {{{ supportsChallengeResponse() + + /** + * Determine if this container supports + * password authentication with challenge response + * + * @return bool + * @access public + */ + function supportsChallengeResponse() + { + return in_array($this->options['cryptType'], array('md5', 'none', '')); + } + + // }}} + // {{{ getCryptType() + + /** + * Returns the selected crypt type for this container + */ + function getCryptType() + { + return($this->options['cryptType']); + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/DBLite.php b/includes/pear/Auth/Container/DBLite.php new file mode 100644 index 0000000..2996108 --- /dev/null +++ b/includes/pear/Auth/Container/DBLite.php @@ -0,0 +1,320 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Reduced storage driver for use against PEAR DB + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: DBLite.php 256753 2008-04-04 07:57:02Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.3.0 + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR DB package + */ +require_once 'DB.php'; + +/** + * A lighter storage driver for fetching login data from a database + * + * This driver is derived from the DB storage container but + * with the user manipulation function removed for smaller file size + * by the PEAR DB abstraction layer to fetch login data. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 256753 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.3.0 + */ +class Auth_Container_DBLite extends Auth_Container +{ + + // {{{ properties + + /** + * Additional options for the storage container + * @var array + */ + var $options = array(); + + /** + * DB object + * @var object + */ + var $db = null; + var $dsn = ''; + + /** + * User that is currently selected from the DB. + * @var string + */ + var $activeUser = ''; + + // }}} + // {{{ Auth_Container_DBLite() [constructor] + + /** + * Constructor of the container class + * + * Initate connection to the database via PEAR::DB + * + * @param string Connection data or DB object + * @return object Returns an error object if something went wrong + */ + function Auth_Container_DBLite($dsn) + { + $this->options['table'] = 'auth'; + $this->options['usernamecol'] = 'username'; + $this->options['passwordcol'] = 'password'; + $this->options['dsn'] = ''; + $this->options['db_fields'] = ''; + $this->options['cryptType'] = 'md5'; + $this->options['db_options'] = array(); + $this->options['db_where'] = ''; + $this->options['auto_quote'] = true; + + if (is_array($dsn)) { + $this->_parseOptions($dsn); + if (empty($this->options['dsn'])) { + PEAR::raiseError('No connection parameters specified!'); + } + } else { + $this->options['dsn'] = $dsn; + } + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @access private + * @param string DSN string + * @return mixed Object on error, otherwise bool + */ + function _connect(&$dsn) + { + $this->log('Auth_Container_DBLite::_connect() called.', AUTH_LOG_DEBUG); + if (is_string($dsn) || is_array($dsn)) { + $this->db =& DB::connect($dsn, $this->options['db_options']); + } elseif (is_subclass_of($dsn, "db_common")) { + $this->db =& $dsn; + } else { + return PEAR::raiseError("Invalid dsn or db object given"); + } + + if (DB::isError($this->db) || PEAR::isError($this->db)) { + return PEAR::raiseError($this->db->getMessage(), $this->db->getCode()); + } else { + return true; + } + } + + // }}} + // {{{ _prepare() + + /** + * Prepare database connection + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * + * @access private + * @return mixed True or a DB error object. + */ + function _prepare() + { + if (!DB::isConnection($this->db)) { + $res = $this->_connect($this->options['dsn']); + if (DB::isError($res) || PEAR::isError($res)) { + return $res; + } + } + if ($this->options['auto_quote'] && $this->db->dsn['phptype'] != 'sqlite') { + if (strpos('.', $this->options['table']) === false) { + $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']); + } else { + $t = explode('.', $this->options['table']); + for ($i = 0, $count = count($t); $i < $count; $i++) + $t[$i] = $this->db->quoteIdentifier($t[$i]); + $this->options['final_table'] = implode('.', $t); + } + $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']); + $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']); + } else { + $this->options['final_table'] = $this->options['table']; + $this->options['final_usernamecol'] = $this->options['usernamecol']; + $this->options['final_passwordcol'] = $this->options['passwordcol']; + } + return true; + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + foreach ($array as $key => $value) { + if (isset($this->options[$key])) { + $this->options[$key] = $value; + } + } + } + + // }}} + // {{{ _quoteDBFields() + + /** + * Quote the db_fields option to avoid the possibility of SQL injection. + * + * @access private + * @return string A properly quoted string that can be concatenated into a + * SELECT clause. + */ + function _quoteDBFields() + { + if (isset($this->options['db_fields'])) { + if (is_array($this->options['db_fields'])) { + if ($this->options['auto_quote']) { + $fields = array(); + foreach ($this->options['db_fields'] as $field) { + $fields[] = $this->db->quoteIdentifier($field); + } + return implode(', ', $fields); + } else { + return implode(', ', $this->options['db_fields']); + } + } else { + if (strlen($this->options['db_fields']) > 0) { + if ($this->options['auto_quote']) { + return $this->db->quoteIdentifier($this->options['db_fields']); + } else { + $this->options['db_fields']; + } + } + } + } + + return ''; + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from database + * + * This function uses the given username to fetch + * the corresponding login data from the database + * table. If an account that matches the passed username + * and password is found, the function returns true. + * Otherwise it returns false. + * + * @param string Username + * @param string Password + * @return mixed Error object or boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_DBLite::fetchData() called.', AUTH_LOG_DEBUG); + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + // Find if db_fields contains a *, if so assume all col are selected + if (is_string($this->options['db_fields']) + && strstr($this->options['db_fields'], '*')) { + $sql_from = "*"; + } else { + $sql_from = $this->options['final_usernamecol']. + ", ".$this->options['final_passwordcol']; + + if (strlen($fields = $this->_quoteDBFields()) > 0) { + $sql_from .= ', '.$fields; + } + } + + $query = "SELECT ".$sql_from. + " FROM ".$this->options['final_table']. + " WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } + if (!is_array($res)) { + $this->activeUser = ''; + return false; + } + if ($this->verifyPassword(trim($password, "\r\n"), + trim($res[$this->options['passwordcol']], "\r\n"), + $this->options['cryptType'])) { + // Store additional field values in the session + foreach ($res as $key => $value) { + if ($key == $this->options['passwordcol'] || + $key == $this->options['usernamecol']) { + continue; + } + + $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG); + + // Use reference to the auth object if exists + // This is because the auth session variable can change so a static call to setAuthData does not make sence + if (is_object($this->_auth_obj)) { + $this->_auth_obj->setAuthData($key, $value); + } else { + Auth::setAuthData($key, $value); + } + } + $this->activeUser = $res[$this->options['usernamecol']]; + return true; + } + $this->activeUser = $res[$this->options['usernamecol']]; + return false; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/File.php b/includes/pear/Auth/Container/File.php new file mode 100644 index 0000000..2b8274c --- /dev/null +++ b/includes/pear/Auth/Container/File.php @@ -0,0 +1,314 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against a generic password file + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Stefan Ekman <stekman@sedata.org> + * @author Martin Jansen <mj@php.net> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Michael Wallner <mike@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: File.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + */ + +/** + * Include PEAR File_Passwd package + */ +require_once "File/Passwd.php"; +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR package for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for fetching login data from an encrypted password file. + * + * This storage container can handle CVS pserver style passwd files. + * + * @category Authentication + * @package Auth + * @author Stefan Ekman <stekman@sedata.org> + * @author Martin Jansen <mj@php.net> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Michael Wallner <mike@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + */ +class Auth_Container_File extends Auth_Container +{ + + // {{{ properties + + /** + * Path to passwd file + * + * @var string + */ + var $pwfile = ''; + + /** + * Options for container + * + * @var array + */ + var $options = array(); + + // }}} + // {{{ Auth_Container_File() [constructor] + + /** + * Constructor of the container class + * + * @param string $filename path to passwd file + * @return object Auth_Container_File new Auth_Container_File object + */ + function Auth_Container_File($filename) { + $this->_setDefaults(); + + // Only file is a valid option here + if(is_array($filename)) { + $this->pwfile = $filename['file']; + $this->_parseOptions($filename); + } else { + $this->pwfile = $filename; + } + } + + // }}} + // {{{ fetchData() + + /** + * Authenticate an user + * + * @param string username + * @param string password + * @return mixed boolean|PEAR_Error + */ + function fetchData($user, $pass) + { + $this->log('Auth_Container_File::fetchData() called.', AUTH_LOG_DEBUG); + return File_Passwd::staticAuth($this->options['type'], $this->pwfile, $user, $pass); + } + + // }}} + // {{{ listUsers() + + /** + * List all available users + * + * @return array + */ + function listUsers() + { + $this->log('Auth_Container_File::listUsers() called.', AUTH_LOG_DEBUG); + + $pw_obj = &$this->_load(); + if (PEAR::isError($pw_obj)) { + return array(); + } + + $users = $pw_obj->listUser(); + if (!is_array($users)) { + return array(); + } + + foreach ($users as $key => $value) { + $retVal[] = array("username" => $key, + "password" => $value['passwd'], + "cvsuser" => $value['system']); + } + + $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG); + + return $retVal; + } + + // }}} + // {{{ addUser() + + /** + * Add a new user to the storage container + * + * @param string username + * @param string password + * @param mixed Additional parameters to File_Password_*::addUser() + * + * @return boolean + */ + function addUser($user, $pass, $additional='') + { + $this->log('Auth_Container_File::addUser() called.', AUTH_LOG_DEBUG); + $params = array($user, $pass); + if (is_array($additional)) { + foreach ($additional as $item) { + $params[] = $item; + } + } else { + $params[] = $additional; + } + + $pw_obj = &$this->_load(); + if (PEAR::isError($pw_obj)) { + return false; + } + + $res = call_user_func_array(array(&$pw_obj, 'addUser'), $params); + if (PEAR::isError($res)) { + return false; + } + + $res = $pw_obj->save(); + if (PEAR::isError($res)) { + return false; + } + + return true; + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @param string Username + * @return boolean + */ + function removeUser($user) + { + $this->log('Auth_Container_File::removeUser() called.', AUTH_LOG_DEBUG); + $pw_obj = &$this->_load(); + if (PEAR::isError($pw_obj)) { + return false; + } + + $res = $pw_obj->delUser($user); + if (PEAR::isError($res)) { + return false; + } + + $res = $pw_obj->save(); + if (PEAR::isError($res)) { + return false; + } + + return true; + } + + // }}} + // {{{ changePassword() + + /** + * Change password for user in the storage container + * + * @param string Username + * @param string The new password + */ + function changePassword($username, $password) + { + $this->log('Auth_Container_File::changePassword() called.', AUTH_LOG_DEBUG); + $pw_obj = &$this->_load(); + if (PEAR::isError($pw_obj)) { + return false; + } + + $res = $pw_obj->changePasswd($username, $password); + if (PEAR::isError($res)) { + return false; + } + + $res = $pw_obj->save(); + if (PEAR::isError($res)) { + return false; + } + + return true; + } + + // }}} + // {{{ _load() + + /** + * Load and initialize the File_Passwd object + * + * @return object File_Passwd_Cvs|PEAR_Error + */ + function &_load() + { + static $pw_obj; + + if (!isset($pw_obj)) { + $this->log('Instanciating File_Password object of type '.$this->options['type'], AUTH_LOG_DEBUG); + $pw_obj = File_Passwd::factory($this->options['type']); + if (PEAR::isError($pw_obj)) { + return $pw_obj; + } + + $pw_obj->setFile($this->pwfile); + + $res = $pw_obj->load(); + if (PEAR::isError($res)) { + return $res; + } + } + + return $pw_obj; + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['type'] = 'Cvs'; + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + foreach ($array as $key => $value) { + if (isset($this->options[$key])) { + $this->options[$key] = $value; + } + } + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/IMAP.php b/includes/pear/Auth/Container/IMAP.php new file mode 100644 index 0000000..2768483 --- /dev/null +++ b/includes/pear/Auth/Container/IMAP.php @@ -0,0 +1,210 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against IMAP servers + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Jeroen Houben <jeroen@terena.nl> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: IMAP.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.0 + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; + +/** + * Include PEAR class for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for fetching login data from an IMAP server + * + * This class is based on LDAP containers, but it very simple. + * By default it connects to localhost:143 + * The constructor will first check if the host:port combination is + * actually reachable. This behaviour can be disabled. + * It then tries to create an IMAP stream (without opening a mailbox) + * If you wish to pass extended options to the connections, you may + * do so by specifying protocol options. + * + * To use this storage containers, you have to use the + * following syntax: + * + * <?php + * ... + * $params = array( + * 'host' => 'mail.example.com', + * 'port' => 143, + * ); + * $myAuth = new Auth('IMAP', $params); + * ... + * + * By default we connect without any protocol options set. However, some + * servers require you to connect with the notls or norsh options set. + * To do this you need to add the following value to the params array: + * 'baseDSN' => '/imap/notls/norsh' + * + * To connect to an SSL IMAP server: + * 'baseDSN' => '/imap/ssl' + * + * To connect to an SSL IMAP server with a self-signed certificate: + * 'baseDSN' => '/imap/ssl/novalidate-cert' + * + * Further options may be available and can be found on the php site at + * http://www.php.net/manual/function.imap-open.php + * + * @category Authentication + * @package Auth + * @author Jeroen Houben <jeroen@terena.nl> + * @author Cipriano Groenendal <cipri@campai.nl> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.2.0 + */ +class Auth_Container_IMAP extends Auth_Container +{ + + // {{{ properties + + /** + * Options for the class + * @var array + */ + var $options = array(); + + // }}} + // {{{ Auth_Container_IMAP() [constructor] + + /** + * Constructor of the container class + * + * @param $params associative array with host, port, baseDSN, checkServer + * and userattr key + * @return object Returns an error object if something went wrong + * @todo Use PEAR Net_IMAP if IMAP extension not loaded + */ + function Auth_Container_IMAP($params) + { + if (!extension_loaded('imap')) { + return PEAR::raiseError('Cannot use IMAP authentication, ' + .'IMAP extension not loaded!', 41, PEAR_ERROR_DIE); + } + $this->_setDefaults(); + + // set parameters (if any) + if (is_array($params)) { + $this->_parseOptions($params); + } + + if ($this->options['checkServer']) { + $this->_checkServer($this->options['timeout']); + } + return true; + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + */ + function _setDefaults() + { + $this->options['host'] = 'localhost'; + $this->options['port'] = 143; + $this->options['baseDSN'] = ''; + $this->options['checkServer'] = true; + $this->options['timeout'] = 20; + } + + // }}} + // {{{ _checkServer() + + /** + * Check if the given server and port are reachable + * + * @access private + */ + function _checkServer() { + $this->log('Auth_Container_IMAP::_checkServer() called.', AUTH_LOG_DEBUG); + $fp = @fsockopen ($this->options['host'], $this->options['port'], + $errno, $errstr, $this->options['timeout']); + if (is_resource($fp)) { + @fclose($fp); + } else { + $message = "Error connecting to IMAP server " + . $this->options['host'] + . ":" . $this->options['port']; + return PEAR::raiseError($message, 41); + } + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + foreach ($array as $key => $value) { + $this->options[$key] = $value; + } + } + + // }}} + // {{{ fetchData() + + /** + * Try to open a IMAP stream using $username / $password + * + * @param string Username + * @param string Password + * @return boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_IMAP::fetchData() called.', AUTH_LOG_DEBUG); + $dsn = '{'.$this->options['host'].':'.$this->options['port'].$this->options['baseDSN'].'}'; + $conn = @imap_open ($dsn, $username, $password, OP_HALFOPEN); + if (is_resource($conn)) { + $this->log('Successfully connected to IMAP server.', AUTH_LOG_DEBUG); + $this->activeUser = $username; + @imap_close($conn); + return true; + } else { + $this->log('Connection to IMAP server failed.', AUTH_LOG_DEBUG); + $this->activeUser = ''; + return false; + } + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/KADM5.php b/includes/pear/Auth/Container/KADM5.php new file mode 100644 index 0000000..2853580 --- /dev/null +++ b/includes/pear/Auth/Container/KADM5.php @@ -0,0 +1,171 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for Authentication on a Kerberos V server. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Andrew Teixeira <ateixeira@gmail.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: KADM5.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.4.0 + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR for error handling + */ +require_once 'PEAR.php'; + +/** + * Storage driver for Authentication on a Kerberos V server. + * + * Available options: + * hostname: The hostname of the kerberos server + * realm: The Kerberos V realm + * timeout: The timeout for checking the server + * checkServer: Set to true to check if the server is running when + * constructing the object + * + * @category Authentication + * @package Auth + * @author Andrew Teixeira <ateixeira@gmail.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.4.0 + */ +class Auth_Container_KADM5 extends Auth_Container { + + // {{{ properties + + /** + * Options for the class + * @var string + */ + var $options = array(); + + // }}} + // {{{ Auth_Container_KADM5() + + /** + * Constructor of the container class + * + * $options can have these keys: + * 'hostname' The hostname of the kerberos server + * 'realm' The Kerberos V realm + * 'timeout' The timeout for checking the server + * 'checkServer' Set to true to check if the server is running when + * constructing the object + * + * @param $options associative array + * @return object Returns an error object if something went wrong + */ + function Auth_Container_KADM5($options) { + if (!extension_loaded('kadm5')) { + return PEAR::raiseError("Cannot use Kerberos V authentication, KADM5 extension not loaded!", 41, PEAR_ERROR_DIE); + } + + $this->_setDefaults(); + + if (isset($options['hostname'])) { + $this->options['hostname'] = $options['hostname']; + } + if (isset($options['realm'])) { + $this->options['realm'] = $options['realm']; + } + if (isset($options['timeout'])) { + $this->options['timeout'] = $options['timeout']; + } + if (isset($options['checkServer'])) { + $this->options['checkServer'] = $options['checkServer']; + } + + if ($this->options['checkServer']) { + $this->_checkServer(); + } + } + + // }}} + // {{{ fetchData() + + /** + * Try to login to the KADM5 server + * + * @param string Username + * @param string Password + * @return boolean + */ + function fetchData($username, $password) { + $this->log('Auth_Container_KADM5::fetchData() called.', AUTH_LOG_DEBUG); + if ( ($username == NULL) || ($password == NULL) ) { + return false; + } + + $server = $this->options['hostname']; + $realm = $this->options['realm']; + $check = @kadm5_init_with_password($server, $realm, $username, $password); + + if ($check == false) { + return false; + } else { + return true; + } + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + */ + function _setDefaults() { + $this->options['hostname'] = 'localhost'; + $this->options['realm'] = NULL; + $this->options['timeout'] = 10; + $this->options['checkServer'] = false; + } + + // }}} + // {{{ _checkServer() + + /** + * Check if the given server and port are reachable + * + * @access private + */ + function _checkServer() { + $fp = @fsockopen ($this->options['hostname'], 88, $errno, $errstr, $this->options['timeout']); + if (is_resource($fp)) { + @fclose($fp); + } else { + $message = "Error connecting to Kerberos V server " + .$this->options['hostname'].":".$this->options['port']; + return PEAR::raiseError($message, 41, PEAR_ERROR_DIE); + } + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Container/LDAP.php b/includes/pear/Auth/Container/LDAP.php new file mode 100644 index 0000000..eaf35cf --- /dev/null +++ b/includes/pear/Auth/Container/LDAP.php @@ -0,0 +1,766 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against an LDAP server + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Jan Wagner <wagner@netsols.de> + * @author Adam Ashley <aashley@php.net> + * @author Hugues Peeters <hugues.peeters@claroline.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: LDAP.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR package for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for fetching login data from LDAP + * + * This class is heavily based on the DB and File containers. By default it + * connects to localhost:389 and searches for uid=$username with the scope + * "sub". If no search base is specified, it will try to determine it via + * the namingContexts attribute. It takes its parameters in a hash, connects + * to the ldap server, binds anonymously, searches for the user, and tries + * to bind as the user with the supplied password. When a group was set, it + * will look for group membership of the authenticated user. If all goes + * well the authentication was successful. + * + * Parameters: + * + * host: localhost (default), ldap.netsols.de or 127.0.0.1 + * port: 389 (default) or 636 or whereever your server runs + * url: ldap://localhost:389/ + * useful for ldaps://, works only with openldap2 ? + * it will be preferred over host and port + * version: LDAP version to use, ususally 2 (default) or 3, + * must be an integer! + * referrals: If set, determines whether the LDAP library automatically + * follows referrals returned by LDAP servers or not. Possible + * values are true (default) or false. + * binddn: If set, searching for user will be done after binding + * as this user, if not set the bind will be anonymous. + * This is reported to make the container work with MS + * Active Directory, but should work with any server that + * is configured this way. + * This has to be a complete dn for now (basedn and + * userdn will not be appended). + * bindpw: The password to use for binding with binddn + * basedn: the base dn of your server + * userdn: gets prepended to basedn when searching for user + * userscope: Scope for user searching: one, sub (default), or base + * userattr: the user attribute to search for (default: uid) + * userfilter: filter that will be added to the search filter + * this way: (&(userattr=username)(userfilter)) + * default: (objectClass=posixAccount) + * attributes: array of additional attributes to fetch from entry. + * these will added to auth data and can be retrieved via + * Auth::getAuthData(). An empty array will fetch all attributes, + * array('') will fetch no attributes at all (default) + * If you add 'dn' as a value to this array, the users DN that was + * used for binding will be added to auth data as well. + * attrformat: The returned format of the additional data defined in the + * 'attributes' option. Two formats are available. + * LDAP returns data formatted in a + * multidimensional array where each array starts with a + * 'count' element providing the number of attributes in the + * entry, or the number of values for attributes. When set + * to this format, the only way to retrieve data from the + * Auth object is by calling getAuthData('attributes'). + * AUTH returns data formatted in a + * structure more compliant with other Auth Containers, + * where each attribute element can be directly called by + * getAuthData() method from Auth. + * For compatibily with previous LDAP container versions, + * the default format is LDAP. + * groupdn: gets prepended to basedn when searching for group + * groupattr: the group attribute to search for (default: cn) + * groupfilter: filter that will be added to the search filter when + * searching for a group: + * (&(groupattr=group)(memberattr=username)(groupfilter)) + * default: (objectClass=groupOfUniqueNames) + * memberattr : the attribute of the group object where the user dn + * may be found (default: uniqueMember) + * memberisdn: whether the memberattr is the dn of the user (default) + * or the value of userattr (usually uid) + * group: the name of group to search for + * groupscope: Scope for group searching: one, sub (default), or base + * start_tls: enable/disable the use of START_TLS encrypted connection + * (default: false) + * debug: Enable/Disable debugging output (default: false) + * try_all: Whether to try all user accounts returned from the search + * or just the first one. (default: false) + * + * To use this storage container, you have to use the following syntax: + * + * <?php + * ... + * + * $a1 = new Auth("LDAP", array( + * 'host' => 'localhost', + * 'port' => '389', + * 'version' => 3, + * 'basedn' => 'o=netsols,c=de', + * 'userattr' => 'uid' + * 'binddn' => 'cn=admin,o=netsols,c=de', + * 'bindpw' => 'password')); + * + * $a2 = new Auth('LDAP', array( + * 'url' => 'ldaps://ldap.netsols.de', + * 'basedn' => 'o=netsols,c=de', + * 'userscope' => 'one', + * 'userdn' => 'ou=People', + * 'groupdn' => 'ou=Groups', + * 'groupfilter' => '(objectClass=posixGroup)', + * 'memberattr' => 'memberUid', + * 'memberisdn' => false, + * 'group' => 'admin' + * )); + * + * $a3 = new Auth('LDAP', array( + * 'host' => 'ldap.netsols.de', + * 'port' => 389, + * 'version' => 3, + * 'referrals' => false, + * 'basedn' => 'dc=netsols,dc=de', + * 'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de', + * 'bindpw' => 'password', + * 'userattr' => 'samAccountName', + * 'userfilter' => '(objectClass=user)', + * 'attributes' => array(''), + * 'group' => 'testing', + * 'groupattr' => 'samAccountName', + * 'groupfilter' => '(objectClass=group)', + * 'memberattr' => 'member', + * 'memberisdn' => true, + * 'groupdn' => 'cn=Users', + * 'groupscope' => 'one', + * 'debug' => true); + * + * The parameter values have to correspond + * to the ones for your LDAP server of course. + * + * When talking to a Microsoft ActiveDirectory server you have to + * use 'samaccountname' as the 'userattr' and follow special rules + * to translate the ActiveDirectory directory names into 'basedn'. + * The 'basedn' for the default 'Users' folder on an ActiveDirectory + * server for the ActiveDirectory Domain (which is not related to + * its DNS name) "win2000.example.org" would be: + * "CN=Users, DC=win2000, DC=example, DC=org' + * where every component of the domain name becomes a DC attribute + * of its own. If you want to use a custom users folder you have to + * replace "CN=Users" with a sequence of "OU" attributes that specify + * the path to your custom folder in reverse order. + * So the ActiveDirectory folder + * "win2000.example.org\Custom\Accounts" + * would become + * "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org' + * + * It seems that binding anonymously to an Active Directory + * is not allowed, so you have to set binddn and bindpw for + * user searching. + * + * LDAP Referrals need to be set to false for AD to work sometimes. + * + * Example a3 shows a full blown and tested example for connection to + * Windows 2000 Active Directory with group mebership checking + * + * Note also that if you want an encrypted connection to an MS LDAP + * server, then, on your webserver, you must specify + * TLS_REQCERT never + * in /etc/ldap/ldap.conf or in the webserver user's ~/.ldaprc (which + * may or may not be read depending on your configuration). + * + * + * @category Authentication + * @package Auth + * @author Jan Wagner <wagner@netsols.de> + * @author Adam Ashley <aashley@php.net> + * @author Hugues Peeters <hugues.peeters@claroline.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + */ +class Auth_Container_LDAP extends Auth_Container +{ + + // {{{ properties + + /** + * Options for the class + * @var array + */ + var $options = array(); + + /** + * Connection ID of LDAP Link + * @var string + */ + var $conn_id = false; + + // }}} + + // {{{ Auth_Container_LDAP() [constructor] + + /** + * Constructor of the container class + * + * @param $params, associative hash with host,port,basedn and userattr key + * @return object Returns an error object if something went wrong + */ + function Auth_Container_LDAP($params) + { + if (false === extension_loaded('ldap')) { + return PEAR::raiseError('Auth_Container_LDAP: LDAP Extension not loaded', + 41, PEAR_ERROR_DIE); + } + + $this->_setDefaults(); + + if (is_array($params)) { + $this->_parseOptions($params); + } + } + + // }}} + // {{{ _prepare() + + /** + * Prepare LDAP connection + * + * This function checks if we have already opened a connection to + * the LDAP server. If that's not the case, a new connection is opened. + * + * @access private + * @return mixed True or a PEAR error object. + */ + function _prepare() + { + if (!$this->_isValidLink()) { + $res = $this->_connect(); + if (PEAR::isError($res)) { + return $res; + } + } + return true; + } + + // }}} + // {{{ _connect() + + /** + * Connect to the LDAP server using the global options + * + * @access private + * @return object Returns a PEAR error object if an error occurs. + */ + function _connect() + { + $this->log('Auth_Container_LDAP::_connect() called.', AUTH_LOG_DEBUG); + // connect + if (isset($this->options['url']) && $this->options['url'] != '') { + $this->log('Connecting with URL', AUTH_LOG_DEBUG); + $conn_params = array($this->options['url']); + } else { + $this->log('Connecting with host:port', AUTH_LOG_DEBUG); + $conn_params = array($this->options['host'], $this->options['port']); + } + + if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) { + $this->log('Connection to server failed.', AUTH_LOG_DEBUG); + $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); + return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41); + } + $this->log('Successfully connected to server', AUTH_LOG_DEBUG); + + // switch LDAP version + if (is_numeric($this->options['version']) && $this->options['version'] > 2) { + $this->log("Switching to LDAP version {$this->options['version']}", AUTH_LOG_DEBUG); + @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']); + + // start TLS if available + if (isset($this->options['start_tls']) && $this->options['start_tls']) { + $this->log("Starting TLS session", AUTH_LOG_DEBUG); + if (@ldap_start_tls($this->conn_id) === false) { + $this->log('Could not start TLS session', AUTH_LOG_DEBUG); + $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); + return PEAR::raiseError('Auth_Container_LDAP: Could not start tls.', 41); + } + } + } + + // switch LDAP referrals + if (is_bool($this->options['referrals'])) { + $this->log("Switching LDAP referrals to " . (($this->options['referrals']) ? 'true' : 'false'), AUTH_LOG_DEBUG); + if (@ldap_set_option($this->conn_id, LDAP_OPT_REFERRALS, $this->options['referrals']) === false) { + $this->log('Could not change LDAP referrals options', AUTH_LOG_DEBUG); + $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); + } + } + + // bind with credentials or anonymously + if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) { + $this->log('Binding with credentials', AUTH_LOG_DEBUG); + $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']); + } else { + $this->log('Binding anonymously', AUTH_LOG_DEBUG); + $bind_params = array($this->conn_id); + } + + // bind for searching + if ((@call_user_func_array('ldap_bind', $bind_params)) === false) { + $this->log('Bind failed', AUTH_LOG_DEBUG); + $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); + $this->_disconnect(); + return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41); + } + $this->log('Binding was successful', AUTH_LOG_DEBUG); + + return true; + } + + // }}} + // {{{ _disconnect() + + /** + * Disconnects (unbinds) from ldap server + * + * @access private + */ + function _disconnect() + { + $this->log('Auth_Container_LDAP::_disconnect() called.', AUTH_LOG_DEBUG); + if ($this->_isValidLink()) { + $this->log('disconnecting from server'); + @ldap_unbind($this->conn_id); + } + } + + // }}} + // {{{ _getBaseDN() + + /** + * Tries to find Basedn via namingContext Attribute + * + * @access private + */ + function _getBaseDN() + { + $this->log('Auth_Container_LDAP::_getBaseDN() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + if ($this->options['basedn'] == "" && $this->_isValidLink()) { + $this->log("basedn not set, searching via namingContexts.", AUTH_LOG_DEBUG); + + $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts")); + + if (@ldap_count_entries($this->conn_id, $result_id) == 1) { + + $this->log("got result for namingContexts", AUTH_LOG_DEBUG); + + $entry_id = @ldap_first_entry($this->conn_id, $result_id); + $attrs = @ldap_get_attributes($this->conn_id, $entry_id); + $basedn = $attrs['namingContexts'][0]; + + if ($basedn != "") { + $this->log("result for namingContexts was $basedn", AUTH_LOG_DEBUG); + $this->options['basedn'] = $basedn; + } + } + @ldap_free_result($result_id); + } + + // if base ist still not set, raise error + if ($this->options['basedn'] == "") { + return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41); + } + return true; + } + + // }}} + // {{{ _isValidLink() + + /** + * determines whether there is a valid ldap conenction or not + * + * @accessd private + * @return boolean + */ + function _isValidLink() + { + if (is_resource($this->conn_id)) { + if (get_resource_type($this->conn_id) == 'ldap link') { + return true; + } + } + return false; + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + */ + function _setDefaults() + { + $this->options['url'] = ''; + $this->options['host'] = 'localhost'; + $this->options['port'] = '389'; + $this->options['version'] = 2; + $this->options['referrals'] = true; + $this->options['binddn'] = ''; + $this->options['bindpw'] = ''; + $this->options['basedn'] = ''; + $this->options['userdn'] = ''; + $this->options['userscope'] = 'sub'; + $this->options['userattr'] = 'uid'; + $this->options['userfilter'] = '(objectClass=posixAccount)'; + $this->options['attributes'] = array(''); // no attributes + $this->options['attrformat'] = 'AUTH'; // returns attribute like other Auth containers + $this->options['group'] = ''; + $this->options['groupdn'] = ''; + $this->options['groupscope'] = 'sub'; + $this->options['groupattr'] = 'cn'; + $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)'; + $this->options['memberattr'] = 'uniqueMember'; + $this->options['memberisdn'] = true; + $this->options['start_tls'] = false; + $this->options['debug'] = false; + $this->options['try_all'] = false; // Try all user ids returned not just the first one + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + $array = $this->_setV12OptionsToV13($array); + + foreach ($array as $key => $value) { + if (array_key_exists($key, $this->options)) { + if ($key == 'attributes') { + if (is_array($value)) { + $this->options[$key] = $value; + } else { + $this->options[$key] = explode(',', $value); + } + } else { + $this->options[$key] = $value; + } + } + } + } + + // }}} + // {{{ _setV12OptionsToV13() + + /** + * Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP + * + * @author Hugues Peeters <hugues.peeters@claroline.net> + * @access private + * @param array + * @return array + */ + function _setV12OptionsToV13($array) + { + if (isset($array['useroc'])) + $array['userfilter'] = "(objectClass=".$array['useroc'].")"; + if (isset($array['groupoc'])) + $array['groupfilter'] = "(objectClass=".$array['groupoc'].")"; + if (isset($array['scope'])) + $array['userscope'] = $array['scope']; + + return $array; + } + + // }}} + // {{{ _scope2function() + + /** + * Get search function for scope + * + * @param string scope + * @return string ldap search function + */ + function _scope2function($scope) + { + switch($scope) { + case 'one': + $function = 'ldap_list'; + break; + case 'base': + $function = 'ldap_read'; + break; + default: + $function = 'ldap_search'; + break; + } + return $function; + } + + // }}} + // {{{ fetchData() + + /** + * Fetch data from LDAP server + * + * Searches the LDAP server for the given username/password + * combination. Escapes all LDAP meta characters in username + * before performing the query. + * + * @param string Username + * @param string Password + * @return boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_LDAP::fetchData() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + $err = $this->_getBaseDN(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + // UTF8 Encode username for LDAPv3 + if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) { + $this->log('UTF8 encoding username for LDAPv3', AUTH_LOG_DEBUG); + $username = utf8_encode($username); + } + + // make search filter + $filter = sprintf('(&(%s=%s)%s)', + $this->options['userattr'], + $this->_quoteFilterString($username), + $this->options['userfilter']); + + // make search base dn + $search_basedn = $this->options['userdn']; + if ($search_basedn != '' && substr($search_basedn, -1) != ',') { + $search_basedn .= ','; + } + $search_basedn .= $this->options['basedn']; + + // attributes + $searchAttributes = $this->options['attributes']; + + // make functions params array + $func_params = array($this->conn_id, $search_basedn, $filter, $searchAttributes); + + // search function to use + $func_name = $this->_scope2function($this->options['userscope']); + + $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG); + + // search + if (($result_id = @call_user_func_array($func_name, $func_params)) === false) { + $this->log('User not found', AUTH_LOG_DEBUG); + } elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results? + + $this->log('User(s) found', AUTH_LOG_DEBUG); + + $first = true; + $entry_id = null; + + do { + + // then get the user dn + if ($first) { + $entry_id = @ldap_first_entry($this->conn_id, $result_id); + $first = false; + } else { + $entry_id = @ldap_next_entry($this->conn_id, $entry_id); + if ($entry_id === false) + break; + } + $user_dn = @ldap_get_dn($this->conn_id, $entry_id); + + // as the dn is not fetched as an attribute, we save it anyway + if (is_array($searchAttributes) && in_array('dn', $searchAttributes)) { + $this->log('Saving DN to AuthData', AUTH_LOG_DEBUG); + $this->_auth_obj->setAuthData('dn', $user_dn); + } + + // fetch attributes + if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) { + + if (is_array($attributes) && isset($attributes['count']) && + $attributes['count'] > 0) { + + // ldap_get_attributes() returns a specific multi dimensional array + // format containing all the attributes and where each array starts + // with a 'count' element providing the number of attributes in the + // entry, or the number of values for attribute. For compatibility + // reasons, it remains the default format returned by LDAP container + // setAuthData(). + // The code below optionally returns attributes in another format, + // more compliant with other Auth containers, where each attribute + // element are directly set in the 'authData' list. This option is + // enabled by setting 'attrformat' to + // 'AUTH' in the 'options' array. + // eg. $this->options['attrformat'] = 'AUTH' + + if ( strtoupper($this->options['attrformat']) == 'AUTH' ) { + $this->log('Saving attributes to Auth data in AUTH format', AUTH_LOG_DEBUG); + unset ($attributes['count']); + foreach ($attributes as $attributeName => $attributeValue ) { + if (is_int($attributeName)) continue; + if (is_array($attributeValue) && isset($attributeValue['count'])) { + unset ($attributeValue['count']); + } + if (count($attributeValue)<=1) $attributeValue = $attributeValue[0]; + $this->log('Storing additional field: '.$attributeName, AUTH_LOG_DEBUG); + $this->_auth_obj->setAuthData($attributeName, $attributeValue); + } + } + else + { + $this->log('Saving attributes to Auth data in LDAP format', AUTH_LOG_DEBUG); + $this->_auth_obj->setAuthData('attributes', $attributes); + } + } + } + @ldap_free_result($result_id); + + // need to catch an empty password as openldap seems to return TRUE + // if anonymous binding is allowed + if ($password != "") { + $this->log("Bind as $user_dn", AUTH_LOG_DEBUG); + + // try binding as this user with the supplied password + if (@ldap_bind($this->conn_id, $user_dn, $password)) { + $this->log('Bind successful', AUTH_LOG_DEBUG); + + // check group if appropiate + if (strlen($this->options['group'])) { + // decide whether memberattr value is a dn or the username + $this->log('Checking group membership', AUTH_LOG_DEBUG); + $return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username); + $this->_disconnect(); + return $return; + } else { + $this->log('Authenticated', AUTH_LOG_DEBUG); + $this->_disconnect(); + return true; // user authenticated + } // checkGroup + } // bind + } // non-empty password + } while ($this->options['try_all'] == true); // interate through entries + } // get results + // default + $this->log('NOT authenticated!', AUTH_LOG_DEBUG); + $this->_disconnect(); + return false; + } + + // }}} + // {{{ checkGroup() + + /** + * Validate group membership + * + * Searches the LDAP server for group membership of the + * supplied username. Quotes all LDAP filter meta characters in + * the user name before querying the LDAP server. + * + * @param string Distinguished Name of the authenticated User + * @return boolean + */ + function checkGroup($user) + { + $this->log('Auth_Container_LDAP::checkGroup() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + // make filter + $filter = sprintf('(&(%s=%s)(%s=%s)%s)', + $this->options['groupattr'], + $this->options['group'], + $this->options['memberattr'], + $this->_quoteFilterString($user), + $this->options['groupfilter']); + + // make search base dn + $search_basedn = $this->options['groupdn']; + if ($search_basedn != '' && substr($search_basedn, -1) != ',') { + $search_basedn .= ','; + } + $search_basedn .= $this->options['basedn']; + + $func_params = array($this->conn_id, $search_basedn, $filter, + array($this->options['memberattr'])); + $func_name = $this->_scope2function($this->options['groupscope']); + + $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG); + + // search + if (($result_id = @call_user_func_array($func_name, $func_params)) != false) { + if (@ldap_count_entries($this->conn_id, $result_id) == 1) { + @ldap_free_result($result_id); + $this->log('User is member of group', AUTH_LOG_DEBUG); + return true; + } + } + // default + $this->log('User is NOT member of group', AUTH_LOG_DEBUG); + return false; + } + + // }}} + // {{{ _quoteFilterString() + + /** + * Escapes LDAP filter special characters as defined in RFC 2254. + * + * @access private + * @param string Filter String + */ + function _quoteFilterString($filter_str) + { + $metas = array( '\\', '*', '(', ')', "\x00"); + $quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00"); + return str_replace($metas, $quoted_metas, $filter_str); + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Container/MDB.php b/includes/pear/Auth/Container/MDB.php new file mode 100644 index 0000000..019a122 --- /dev/null +++ b/includes/pear/Auth/Container/MDB.php @@ -0,0 +1,625 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against PEAR MDB + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Lorenzo Alberton <l.alberton@quipo.it> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: MDB.php 256753 2008-04-04 07:57:02Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.3 + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR MDB package + */ +require_once 'MDB.php'; + +/** + * Storage driver for fetching login data from a database + * + * This storage driver can use all databases which are supported + * by the PEAR MDB abstraction layer to fetch login data. + * + * @category Authentication + * @package Auth + * @author Lorenzo Alberton <l.alberton@quipo.it> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 256753 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.2.3 + */ +class Auth_Container_MDB extends Auth_Container +{ + + // {{{ properties + + /** + * Additional options for the storage container + * @var array + */ + var $options = array(); + + /** + * MDB object + * @var object + */ + var $db = null; + var $dsn = ''; + + /** + * User that is currently selected from the DB. + * @var string + */ + var $activeUser = ''; + + // }}} + // {{{ Auth_Container_MDB() [constructor] + + /** + * Constructor of the container class + * + * Initate connection to the database via PEAR::MDB + * + * @param string Connection data or MDB object + * @return object Returns an error object if something went wrong + */ + function Auth_Container_MDB($dsn) + { + $this->_setDefaults(); + + if (is_array($dsn)) { + $this->_parseOptions($dsn); + if (empty($this->options['dsn'])) { + PEAR::raiseError('No connection parameters specified!'); + } + } else { + $this->options['dsn'] = $dsn; + } + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @access private + * @param mixed DSN string | array | mdb object + * @return mixed Object on error, otherwise bool + */ + function _connect($dsn) + { + $this->log('Auth_Container_MDB::_connect() called.', AUTH_LOG_DEBUG); + if (is_string($dsn) || is_array($dsn)) { + $this->db =& MDB::connect($dsn, $this->options['db_options']); + } elseif (is_subclass_of($dsn, 'mdb_common')) { + $this->db = $dsn; + } elseif (is_object($dsn) && MDB::isError($dsn)) { + return PEAR::raiseError($dsn->getMessage(), $dsn->code); + } else { + return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + + } + + if (MDB::isError($this->db) || PEAR::isError($this->db)) { + return PEAR::raiseError($this->db->getMessage(), $this->db->code); + } + + if ($this->options['auto_quote']) { + if (strpos('.', $this->options['table']) === false) { + $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']); + } else { + $t = explode('.', $this->options['table']); + for ($i = 0, $count = count($t); $i < $count; $i++) + $t[$i] = $this->db->quoteIdentifier($t[$i]); + $this->options['final_table'] = implode('.', $t); + } + $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']); + $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']); + } else { + $this->options['final_table'] = $this->options['table']; + $this->options['final_usernamecol'] = $this->options['usernamecol']; + $this->options['final_passwordcol'] = $this->options['passwordcol']; + } + + return true; + } + + // }}} + // {{{ _prepare() + + /** + * Prepare database connection + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * + * @access private + * @return mixed True or a MDB error object. + */ + function _prepare() + { + if (is_subclass_of($this->db, 'mdb_common')) { + return true; + } + return $this->_connect($this->options['dsn']); + } + + // }}} + // {{{ query() + + /** + * Prepare query to the database + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * After that the query is passed to the database. + * + * @access public + * @param string Query string + * @return mixed a MDB_result object or MDB_OK on success, a MDB + * or PEAR error on failure + */ + function query($query) + { + $this->log('Auth_Container_MDB::query() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return $err; + } + return $this->db->query($query); + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['table'] = 'auth'; + $this->options['usernamecol'] = 'username'; + $this->options['passwordcol'] = 'password'; + $this->options['dsn'] = ''; + $this->options['db_fields'] = ''; + $this->options['cryptType'] = 'md5'; + $this->options['db_options'] = array(); + $this->options['db_where'] = ''; + $this->options['auto_quote'] = true; + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + foreach ($array as $key => $value) { + if (isset($this->options[$key])) { + $this->options[$key] = $value; + } + } + } + + // }}} + // {{{ _quoteDBFields() + + /** + * Quote the db_fields option to avoid the possibility of SQL injection. + * + * @access private + * @return string A properly quoted string that can be concatenated into a + * SELECT clause. + */ + function _quoteDBFields() + { + if (isset($this->options['db_fields'])) { + if (is_array($this->options['db_fields'])) { + if ($this->options['auto_quote']) { + $fields = array(); + foreach ($this->options['db_fields'] as $field) { + $fields[] = $this->db->quoteIdentifier($field); + } + return implode(', ', $fields); + } else { + return implode(', ', $this->options['db_fields']); + } + } else { + if (strlen($this->options['db_fields']) > 0) { + if ($this->options['auto_quote']) { + return $this->db->quoteIdentifier($this->options['db_fields']); + } else { + return $this->options['db_fields']; + } + } + } + } + + return ''; + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from database + * + * This function uses the given username to fetch + * the corresponding login data from the database + * table. If an account that matches the passed username + * and password is found, the function returns true. + * Otherwise it returns false. + * + * @param string Username + * @param string Password + * @param boolean If true password is secured using a md5 hash + * the frontend and auth are responsible for making sure the container supports + * challenge response password authentication + * @return mixed Error object or boolean + */ + function fetchData($username, $password, $isChallengeResponse=false) + { + $this->log('Auth_Container_MDB::fetchData() called.', AUTH_LOG_DEBUG); + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + //Check if db_fields contains a *, if so assume all columns are selected + if (is_string($this->options['db_fields']) + && strstr($this->options['db_fields'], '*')) { + $sql_from = '*'; + } else { + $sql_from = $this->options['final_usernamecol']. + ", ".$this->options['final_passwordcol']; + + if (strlen($fields = $this->_quoteDBFields()) > 0) { + $sql_from .= ', '.$fields; + } + } + + $query = sprintf("SELECT %s FROM %s WHERE %s = %s", + $sql_from, + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->db->getTextValue($username) + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->db->getRow($query, null, null, null, MDB_FETCHMODE_ASSOC); + + if (MDB::isError($res) || PEAR::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } + if (!is_array($res)) { + $this->activeUser = ''; + return false; + } + + // Perform trimming here before the hashing + $password = trim($password, "\r\n"); + $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n"); + + // If using Challenge Response md5 the pass with the secret + if ($isChallengeResponse) { + $res[$this->options['passwordcol']] = + md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']); + // UGLY cannot avoid without modifying verifyPassword + if ($this->options['cryptType'] == 'md5') { + $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]); + } + } + + if ($this->verifyPassword($password, + $res[$this->options['passwordcol']], + $this->options['cryptType'])) { + // Store additional field values in the session + foreach ($res as $key => $value) { + if ($key == $this->options['passwordcol'] || + $key == $this->options['usernamecol']) { + continue; + } + + $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG); + // Use reference to the auth object if exists + // This is because the auth session variable can change so a static + // call to setAuthData does not make sense + $this->_auth_obj->setAuthData($key, $value); + } + return true; + } + + $this->activeUser = $res[$this->options['usernamecol']]; + return false; + } + + // }}} + // {{{ listUsers() + + /** + * Returns a list of users from the container + * + * @return mixed array|PEAR_Error + * @access public + */ + function listUsers() + { + $this->log('Auth_Container_MDB::listUsers() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + $retVal = array(); + + //Check if db_fields contains a *, if so assume all columns are selected + if ( is_string($this->options['db_fields']) + && strstr($this->options['db_fields'], '*')) { + $sql_from = '*'; + } else { + $sql_from = $this->options['final_usernamecol'] + .', '.$this->options['final_passwordcol']; + + if (strlen($fields = $this->_quoteDBFields()) > 0) { + $sql_from .= ', '.$fields; + } + } + + $query = sprintf('SELECT %s FROM %s', + $sql_from, + $this->options['final_table'] + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " WHERE ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->db->getAll($query, null, null, null, MDB_FETCHMODE_ASSOC); + + if (MDB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + foreach ($res as $user) { + $user['username'] = $user[$this->options['usernamecol']]; + $retVal[] = $user; + } + } + $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG); + return $retVal; + } + + // }}} + // {{{ addUser() + + /** + * Add user to the storage container + * + * @access public + * @param string Username + * @param string Password + * @param mixed Additional information that are stored in the DB + * + * @return mixed True on success, otherwise error object + */ + function addUser($username, $password, $additional = "") + { + $this->log('Auth_Container_MDB::addUser() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') { + $cryptFunction = 'strval'; + } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) { + $cryptFunction = $this->options['cryptType']; + } else { + $cryptFunction = 'md5'; + } + + $password = $cryptFunction($password); + + $additional_key = ''; + $additional_value = ''; + + if (is_array($additional)) { + foreach ($additional as $key => $value) { + if ($this->options['auto_quote']) { + $additional_key .= ', ' . $this->db->quoteIdentifier($key); + } else { + $additional_key .= ', ' . $key; + } + $additional_value .= ', ' . $this->db->getTextValue($value); + } + } + + $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)", + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->options['final_passwordcol'], + $additional_key, + $this->db->getTextValue($username), + $this->db->getTextValue($password), + $additional_value + ); + + $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (MDB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->code); + } + return true; + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @access public + * @param string Username + * + * @return mixed True on success, otherwise error object + */ + function removeUser($username) + { + $this->log('Auth_Container_MDB::removeUser() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + $query = sprintf("DELETE FROM %s WHERE %s = %s", + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->db->getTextValue($username) + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (MDB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->code); + } + return true; + } + + // }}} + // {{{ changePassword() + + /** + * Change password for user in the storage container + * + * @param string Username + * @param string The new password (plain text) + */ + function changePassword($username, $password) + { + $this->log('Auth_Container_MDB::changePassword() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') { + $cryptFunction = 'strval'; + } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) { + $cryptFunction = $this->options['cryptType']; + } else { + $cryptFunction = 'md5'; + } + + $password = $cryptFunction($password); + + $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s", + $this->options['final_table'], + $this->options['final_passwordcol'], + $this->db->getTextValue($password), + $this->options['final_usernamecol'], + $this->db->getTextValue($username) + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (MDB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->code); + } + return true; + } + + // }}} + // {{{ supportsChallengeResponse() + + /** + * Determine if this container supports + * password authentication with challenge response + * + * @return bool + * @access public + */ + function supportsChallengeResponse() + { + return in_array($this->options['cryptType'], array('md5', 'none', '')); + } + + // }}} + // {{{ getCryptType() + + /** + * Returns the selected crypt type for this container + * + * @return string Function used to crypt the password + */ + function getCryptType() + { + return $this->options['cryptType']; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/MDB2.php b/includes/pear/Auth/Container/MDB2.php new file mode 100644 index 0000000..a3c26b7 --- /dev/null +++ b/includes/pear/Auth/Container/MDB2.php @@ -0,0 +1,624 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against PEAR MDB2 + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Lorenzo Alberton <l.alberton@quipo.it> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: MDB2.php 256753 2008-04-04 07:57:02Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.3.0 + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR MDB2 package + */ +require_once 'MDB2.php'; + +/** + * Storage driver for fetching login data from a database + * + * This storage driver can use all databases which are supported + * by the PEAR MDB2 abstraction layer to fetch login data. + * + * @category Authentication + * @package Auth + * @author Lorenzo Alberton <l.alberton@quipo.it> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 256753 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.3.0 + */ +class Auth_Container_MDB2 extends Auth_Container +{ + + // {{{ properties + + /** + * Additional options for the storage container + * @var array + */ + var $options = array(); + + /** + * MDB object + * @var object + */ + var $db = null; + var $dsn = ''; + + /** + * User that is currently selected from the DB. + * @var string + */ + var $activeUser = ''; + + // }}} + // {{{ Auth_Container_MDB2() [constructor] + + /** + * Constructor of the container class + * + * Initate connection to the database via PEAR::MDB2 + * + * @param string Connection data or MDB2 object + * @return object Returns an error object if something went wrong + */ + function Auth_Container_MDB2($dsn) + { + $this->_setDefaults(); + + if (is_array($dsn)) { + $this->_parseOptions($dsn); + if (empty($this->options['dsn'])) { + PEAR::raiseError('No connection parameters specified!'); + } + } else { + $this->options['dsn'] = $dsn; + } + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @access private + * @param mixed DSN string | array | mdb object + * @return mixed Object on error, otherwise bool + */ + function _connect($dsn) + { + $this->log('Auth_Container_MDB2::_connect() called.', AUTH_LOG_DEBUG); + if (is_string($dsn) || is_array($dsn)) { + $this->db =& MDB2::connect($dsn, $this->options['db_options']); + } elseif (is_subclass_of($dsn, 'MDB2_Driver_Common')) { + $this->db = $dsn; + } elseif (is_object($dsn) && MDB2::isError($dsn)) { + return PEAR::raiseError($dsn->getMessage(), $dsn->code); + } else { + return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + + } + + if (MDB2::isError($this->db) || PEAR::isError($this->db)) { + return PEAR::raiseError($this->db->getMessage(), $this->db->code); + } + + if ($this->options['auto_quote']) { + if (strpos('.', $this->options['table']) === false) { + $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table'], true); + } else { + $t = explode('.', $this->options['table']); + for ($i = 0, $count = count($t); $i < $count; $i++) + $t[$i] = $this->db->quoteIdentifier($t[$i], true); + $this->options['final_table'] = implode('.', $t); + } + $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol'], true); + $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol'], true); + } else { + $this->options['final_table'] = $this->options['table']; + $this->options['final_usernamecol'] = $this->options['usernamecol']; + $this->options['final_passwordcol'] = $this->options['passwordcol']; + } + + return true; + } + + // }}} + // {{{ _prepare() + + /** + * Prepare database connection + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * + * @access private + * @return mixed True or a MDB error object. + */ + function _prepare() + { + if (is_subclass_of($this->db, 'MDB2_Driver_Common')) { + return true; + } + return $this->_connect($this->options['dsn']); + } + + // }}} + // {{{ query() + + /** + * Prepare query to the database + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * After that the query is passed to the database. + * + * @access public + * @param string Query string + * @return mixed a MDB_result object or MDB_OK on success, a MDB + * or PEAR error on failure + */ + function query($query) + { + $this->log('Auth_Container_MDB2::query() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return $err; + } + return $this->db->exec($query); + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['table'] = 'auth'; + $this->options['usernamecol'] = 'username'; + $this->options['passwordcol'] = 'password'; + $this->options['dsn'] = ''; + $this->options['db_fields'] = ''; + $this->options['cryptType'] = 'md5'; + $this->options['db_options'] = array(); + $this->options['db_where'] = ''; + $this->options['auto_quote'] = true; + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + foreach ($array as $key => $value) { + if (isset($this->options[$key])) { + $this->options[$key] = $value; + } + } + } + + // }}} + // {{{ _quoteDBFields() + + /** + * Quote the db_fields option to avoid the possibility of SQL injection. + * + * @access private + * @return string A properly quoted string that can be concatenated into a + * SELECT clause. + */ + function _quoteDBFields() + { + if (isset($this->options['db_fields'])) { + if (is_array($this->options['db_fields'])) { + if ($this->options['auto_quote']) { + $fields = array(); + foreach ($this->options['db_fields'] as $field) { + $fields[] = $this->db->quoteIdentifier($field, true); + } + return implode(', ', $fields); + } else { + return implode(', ', $this->options['db_fields']); + } + } else { + if (strlen($this->options['db_fields']) > 0) { + if ($this->options['auto_quote']) { + return $this->db->quoteIdentifier($this->options['db_fields'], true); + } else { + return $this->options['db_fields']; + } + } + } + } + + return ''; + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from database + * + * This function uses the given username to fetch + * the corresponding login data from the database + * table. If an account that matches the passed username + * and password is found, the function returns true. + * Otherwise it returns false. + * + * @param string Username + * @param string Password + * @param boolean If true password is secured using a md5 hash + * the frontend and auth are responsible for making sure the container supports + * challenge response password authentication + * @return mixed Error object or boolean + */ + function fetchData($username, $password, $isChallengeResponse=false) + { + $this->log('Auth_Container_MDB2::fetchData() called.', AUTH_LOG_DEBUG); + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + //Check if db_fields contains a *, if so assume all columns are selected + if (is_string($this->options['db_fields']) + && strstr($this->options['db_fields'], '*')) { + $sql_from = '*'; + } else { + $sql_from = $this->options['final_usernamecol']. + ", ".$this->options['final_passwordcol']; + + if (strlen($fields = $this->_quoteDBFields()) > 0) { + $sql_from .= ', '.$fields; + } + } + $query = sprintf("SELECT %s FROM %s WHERE %s = %s", + $sql_from, + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->db->quote($username, 'text') + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG); + + $res = $this->db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($res) || PEAR::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } + if (!is_array($res)) { + $this->activeUser = ''; + return false; + } + + // Perform trimming here before the hashing + $password = trim($password, "\r\n"); + $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n"); + // If using Challenge Response md5 the pass with the secret + if ($isChallengeResponse) { + $res[$this->options['passwordcol']] = + md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']); + // UGLY cannot avoid without modifying verifyPassword + if ($this->options['cryptType'] == 'md5') { + $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]); + } + } + if ($this->verifyPassword($password, + $res[$this->options['passwordcol']], + $this->options['cryptType'])) { + // Store additional field values in the session + foreach ($res as $key => $value) { + if ($key == $this->options['passwordcol'] || + $key == $this->options['usernamecol']) { + continue; + } + + $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG); + + // Use reference to the auth object if exists + // This is because the auth session variable can change so a static call to setAuthData does not make sense + $this->_auth_obj->setAuthData($key, $value); + } + return true; + } + + $this->activeUser = $res[$this->options['usernamecol']]; + return false; + } + + // }}} + // {{{ listUsers() + + /** + * Returns a list of users from the container + * + * @return mixed array|PEAR_Error + * @access public + */ + function listUsers() + { + $this->log('Auth_Container_MDB2::listUsers() called.', AUTH_LOG_DEBUG); + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + $retVal = array(); + + //Check if db_fields contains a *, if so assume all columns are selected + if ( is_string($this->options['db_fields']) + && strstr($this->options['db_fields'], '*')) { + $sql_from = '*'; + } else { + $sql_from = $this->options['final_usernamecol']. + ", ".$this->options['final_passwordcol']; + + if (strlen($fields = $this->_quoteDBFields()) > 0) { + $sql_from .= ', '.$fields; + } + } + + $query = sprintf('SELECT %s FROM %s', + $sql_from, + $this->options['final_table'] + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " WHERE ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG); + + $res = $this->db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + foreach ($res as $user) { + $user['username'] = $user[$this->options['usernamecol']]; + $retVal[] = $user; + } + } + $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG); + return $retVal; + } + + // }}} + // {{{ addUser() + + /** + * Add user to the storage container + * + * @access public + * @param string Username + * @param string Password + * @param mixed Additional information that are stored in the DB + * + * @return mixed True on success, otherwise error object + */ + function addUser($username, $password, $additional = "") + { + $this->log('Auth_Container_MDB2::addUser() called.', AUTH_LOG_DEBUG); + + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') { + $cryptFunction = 'strval'; + } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) { + $cryptFunction = $this->options['cryptType']; + } else { + $cryptFunction = 'md5'; + } + + $password = $cryptFunction($password); + + $additional_key = ''; + $additional_value = ''; + + if (is_array($additional)) { + foreach ($additional as $key => $value) { + if ($this->options['auto_quote']) { + $additional_key .= ', ' . $this->db->quoteIdentifier($key, true); + } else { + $additional_key .= ', ' . $key; + } + $additional_value .= ', ' . $this->db->quote($value, 'text'); + } + } + + $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)", + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->options['final_passwordcol'], + $additional_key, + $this->db->quote($username, 'text'), + $this->db->quote($password, 'text'), + $additional_value + ); + + $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (MDB2::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->code); + } + return true; + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @access public + * @param string Username + * + * @return mixed True on success, otherwise error object + */ + function removeUser($username) + { + $this->log('Auth_Container_MDB2::removeUser() called.', AUTH_LOG_DEBUG); + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + $query = sprintf("DELETE FROM %s WHERE %s = %s", + $this->options['final_table'], + $this->options['final_usernamecol'], + $this->db->quote($username, 'text') + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (MDB2::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->code); + } + return true; + } + + // }}} + // {{{ changePassword() + + /** + * Change password for user in the storage container + * + * @param string Username + * @param string The new password (plain text) + */ + function changePassword($username, $password) + { + $this->log('Auth_Container_MDB2::changePassword() called.', AUTH_LOG_DEBUG); + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') { + $cryptFunction = 'strval'; + } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) { + $cryptFunction = $this->options['cryptType']; + } else { + $cryptFunction = 'md5'; + } + + $password = $cryptFunction($password); + + $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s", + $this->options['final_table'], + $this->options['final_passwordcol'], + $this->db->quote($password, 'text'), + $this->options['final_usernamecol'], + $this->db->quote($username, 'text') + ); + + // check if there is an optional parameter db_where + if ($this->options['db_where'] != '') { + // there is one, so add it to the query + $query .= " AND ".$this->options['db_where']; + } + + $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG); + + $res = $this->query($query); + + if (MDB2::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->code); + } + return true; + } + + // }}} + // {{{ supportsChallengeResponse() + + /** + * Determine if this container supports + * password authentication with challenge response + * + * @return bool + * @access public + */ + function supportsChallengeResponse() + { + return in_array($this->options['cryptType'], array('md5', 'none', '')); + } + + // }}} + // {{{ getCryptType() + + /** + * Returns the selected crypt type for this container + * + * @return string Function used to crypt the password + */ + function getCryptType() + { + return $this->options['cryptType']; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/Multiple.php b/includes/pear/Auth/Container/Multiple.php new file mode 100644 index 0000000..08f131d --- /dev/null +++ b/includes/pear/Auth/Container/Multiple.php @@ -0,0 +1,188 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for using multiple storage drivers in a fall through fashion + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Multiple.php 289653 2009-10-15 04:50:43Z aashley $ + * @since File available since Release 1.5.0 + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR package for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for using multiple storage drivers in a fall through fashion + * + * This storage driver provides a mechanism for working through multiple + * storage drivers until either one allows successful login or the list is + * exhausted. + * + * This container takes an array of options of the following form: + * + * array( + * array( + * 'type' => <standard container type name>, + * 'options' => <normal array of options for container>, + * ), + * ); + * + * Full example: + * + * $options = array( + * array( + * 'type' => 'DB', + * 'options' => array( + * 'dsn' => "mysql://user:password@localhost/database", + * ), + * ), + * array( + * 'type' => 'Array', + * 'options' => array( + * 'cryptType' => 'md5', + * 'users' => array( + * 'admin' => md5('password'), + * ), + * ), + * ), + * ); + * + * $auth = new Auth('Multiple', $options); + * + * @category Authentication + * @package Auth + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 289653 $ + * @since File available since Release 1.5.0 + */ + +class Auth_Container_Multiple extends Auth_Container { + + // {{{ properties + + /** + * The options for each container + * + * @var array $options + */ + var $options = array(); + + /** + * The instanciated containers + * + * @var array $containers + */ + var $containers = array(); + + // }}} + // {{{ Auth_Container_Multiple() + + /** + * Constructor for Array Container + * + * @param array $data Options for the container + * @return void + */ + function Auth_Container_Multiple($options) + { + if (!is_array($options)) { + PEAR::raiseError('The options for Auth_Container_Multiple must be an array'); + } + if (count($options) < 1) { + PEAR::raiseError('You must define at least one sub container to use in Auth_Container_Multiple'); + } + foreach ($options as $option) { + if (!isset($option['type'])) { + PEAR::raiseError('No type defined for sub container'); + } + } + $this->options = $options; + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from array + * + * This function uses the given username to fetch the corresponding + * login data from the array. If an account that matches the passed + * username and password is found, the function returns true. + * Otherwise it returns false. + * + * @param string Username + * @param string Password + * @return boolean|PEAR_Error Error object or boolean + */ + function fetchData($user, $pass) + { + $this->log('Auth_Container_Multiple::fetchData() called.', AUTH_LOG_DEBUG); + + foreach ($this->options as $key => $options) { + + $this->log('Using Container '.$key.' of type '.$options['type'].'.', AUTH_LOG_DEBUG); + + if (isset($this->containers[$key]) && is_a($this->containers[$key], 'Auth_Container')) { + + $container = &$this->containers[$key]; + + } else { + + $this->containers[$key] = &$this->_auth_obj->_factory($options['type'], $options['options']); + $this->containers[$key]->_auth_obj = &$this->_auth_obj; + $container = &$this->containers[$key]; + + } + + $result = $container->fetchData($user, $pass); + + if (PEAR::isError($result)) { + + $this->log('Container '.$key.': '.$result->getMessage(), AUTH_LOG_DEBUG); + return $result; + + } elseif ($result == true) { + + $this->log('Container '.$key.': Authentication successful.', AUTH_LOG_DEBUG); + return true; + + } else { + + $this->log('Container '.$key.': Authentication failed.', AUTH_LOG_DEBUG); + + } + + } + + $this->log('Auth_Container_Multiple: All containers rejected user credentials.', AUTH_LOG_DEBUG); + + return false; + + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Container/NetVPOPMaild.php b/includes/pear/Auth/Container/NetVPOPMaild.php new file mode 100644 index 0000000..634fab5 --- /dev/null +++ b/includes/pear/Auth/Container/NetVPOPMaild.php @@ -0,0 +1,129 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use with a Vpopmaild server + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Bill Shupp <hostmaster@shupp.org> + * @author Stefan Ekman <stekman@sedata.org> + * @author Martin Jansen <mj@php.net> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.0 + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR package for error handling + */ +require_once 'PEAR.php'; +/** + * Include PEAR Net_Vpopmaild package + */ +require_once 'Net/Vpopmaild.php'; + +/** + * Storage driver for Authentication on a Vpopmaild server. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: 1.5.4 File: $Revision: 256741 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.6.0 + */ +class Auth_Container_Vpopmaild extends Auth_Container +{ + + /** + * Vpopmaild Server + * @var string + */ + var $server = 'localhost'; + + /** + * Vpopmaild Server port + * @var string + */ + var $port = 89; + + /** + * Constructor of the container class + * + * @param $server string server or server:port combination + * @return object Returns an error object if something went wrong + */ + function Auth_Container_Vpopmaild($server=null) + { + if (isset($server) && !is_null($server)) { + if (is_array($server)) { + if (isset($server['host'])) { + $this->server = $server['host']; + } + if (isset($server['port'])) { + $this->port = $server['port']; + } + } else { + if (strstr($server, ':')) { + $serverparts = explode(':', trim($server)); + $this->server = $serverparts[0]; + $this->port = $serverparts[1]; + } else { + $this->server = $server; + } + } + } + } + + /** + * fetchData() + * + * Try to login to the Vpopmaild server + * + * @param string username + * @param string password + * + * @return boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_Vpopmaild::fetchData() called.', AUTH_LOG_DEBUG); + $vpopmaild =& new Net_Vpopmaild(); + // Connect + try { + $res = $vpopmaild->connect($this->server, $this->port, $this->method); + } catch (Net_Vpopmaild_FatalException $e) { + $this->log('Connection to Vpopmaild server failed.', AUTH_LOG_DEBUG); + return PEAR::raiseError($e->getMessage(), $e->getCode()); + } + // Authenticate + try { + $result = $vpopmaild->clogin($username, $password); + $vpopmaild->quit(); + } catch (Net_Vpopmaild_Exception $e) { + return PEAR::raiseError($e->getMessage(), $e->getCode()); + } + return $result; + } +} +?> diff --git a/includes/pear/Auth/Container/PEAR.php b/includes/pear/Auth/Container/PEAR.php new file mode 100644 index 0000000..1de9b82 --- /dev/null +++ b/includes/pear/Auth/Container/PEAR.php @@ -0,0 +1,165 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against PEAR website + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Yavor Shahpasov <yavo@netsmart.com.cy> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: PEAR.php 289652 2009-10-15 04:42:18Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.3.0 + */ + +/** + * Include PEAR HTTP_Client. + */ +require_once 'HTTP/Client.php'; +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; + +/** + * Storage driver for authenticating against PEAR website + * + * This driver provides a method for authenticating against the pear.php.net + * authentication system. + * + * Supports two options: + * - "url": The base URL with schema to authenticate against + * - "karma": An array of karma levels which the user needs one of. + * When empty, no karma level is required. + * + * @category Authentication + * @package Auth + * @author Yavor Shahpasov <yavo@netsmart.com.cy> + * @author Adam Ashley <aashley@php.net> + * @author Adam Harvey <aharvey@php.net> + * @copyright 2001-2007 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 289652 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.3.0 + */ +class Auth_Container_Pear extends Auth_Container +{ + // {{{ properties + + /** + * URL to connect to, with schema + * + * @var string + */ + var $url = 'https://pear.php.net/rest-login.php/'; + + /** + * Array of karma levels the user can have. + * A user needs only one of the levels to succeed login. + * No levels mean that only username and password need to match + * + * @var array + */ + var $karma = array(); + + // }}} + // {{{ Auth_Container_Pear() [constructor] + + /** + * Constructor + * + * Accepts options "url" and "karma", see class docs. + * + * @param array $data Array of options + * + * @return void + */ + function Auth_Container_Pear($data = null) + { + if (!is_array($data)) { + PEAR::raiseError('The options for Auth_Container_Pear must be an array'); + } + if (isset($data['karma'])) { + if (is_array($data['karma'])) { + $this->karma = $data['karma']; + } else { + $this->karma = array($data['karma']); + } + } + + if (isset($data['url'])) { + $this->url = $data['url']; + } + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from pear.php.net + * + * This function uses the given username and password to authenticate + * against the pear.php.net website + * + * @param string Username + * @param string Password + * @return mixed Error object or boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_PEAR::fetchData() called.', AUTH_LOG_DEBUG); + + $client = new HTTP_Client; + + $this->log('Auth_Container_PEAR::fetchData() getting salt.', AUTH_LOG_DEBUG); + $code = $client->get($this->url . '/getsalt'); + if ($code instanceof PEAR_Error) { + return $code; + } + if ($code != 200) { + return PEAR::raiseError('Bad response to salt request.', $code); + } + $resp = $client->currentResponse(); + $salt = $resp['body']; + + $this->log('Auth_Container_PEAR::fetchData() calling validate.', AUTH_LOG_DEBUG); + $postOptions = array( + 'username' => $username, + 'password' => md5($salt . md5($password)) + ); + if (is_array($this->karma) && count($this->karma) > 0) { + $postOptions['karma'] = implode(',', $this->karma); + } + + $code = $client->post($this->url . '/validate', $postOptions); + if ($code instanceof PEAR_Error) { + return $code; + } + if ($code != 200) { + return PEAR::raiseError('Bad response to validate request.', $code); + } + $resp = $client->currentResponse(); + + list($code, $message) = explode(' ', $resp['body'], 2); + if ($code != 8) { + return PEAR::raiseError($message, $code); + } + return true; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/POP3.php b/includes/pear/Auth/Container/POP3.php new file mode 100644 index 0000000..0bfab1b --- /dev/null +++ b/includes/pear/Auth/Container/POP3.php @@ -0,0 +1,145 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against a POP3 server + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Stefan Ekman <stekman@sedata.org> + * @author Martin Jansen <mj@php.net> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: POP3.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.0 + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR package for error handling + */ +require_once 'PEAR.php'; +/** + * Include PEAR Net_POP3 package + */ +require_once 'Net/POP3.php'; + +/** + * Storage driver for Authentication on a POP3 server. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.2.0 + */ +class Auth_Container_POP3 extends Auth_Container +{ + + // {{{ properties + + /** + * POP3 Server + * @var string + */ + var $server='localhost'; + + /** + * POP3 Server port + * @var string + */ + var $port='110'; + + /** + * POP3 Authentication method + * + * Prefered POP3 authentication method. Acceptable values: + * Boolean TRUE - Use Net_POP3's autodetection + * String 'DIGEST-MD5','CRAM-MD5','LOGIN','PLAIN','APOP','USER' + * - Attempt this authentication style first + * then fallback to autodetection. + * @var mixed + */ + var $method=true; + + // }}} + // {{{ Auth_Container_POP3() [constructor] + + /** + * Constructor of the container class + * + * @param $server string server or server:port combination + * @return object Returns an error object if something went wrong + */ + function Auth_Container_POP3($server=null) + { + if (isset($server) && !is_null($server)) { + if (is_array($server)) { + if (isset($server['host'])) { + $this->server = $server['host']; + } + if (isset($server['port'])) { + $this->port = $server['port']; + } + if (isset($server['method'])) { + $this->method = $server['method']; + } + } else { + if (strstr($server, ':')) { + $serverparts = explode(':', trim($server)); + $this->server = $serverparts[0]; + $this->port = $serverparts[1]; + } else { + $this->server = $server; + } + } + } + } + + // }}} + // {{{ fetchData() + + /** + * Try to login to the POP3 server + * + * @param string Username + * @param string Password + * @return boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_POP3::fetchData() called.', AUTH_LOG_DEBUG); + $pop3 =& new Net_POP3(); + $res = $pop3->connect($this->server, $this->port, $this->method); + if (!$res) { + $this->log('Connection to POP3 server failed.', AUTH_LOG_DEBUG); + return $res; + } + $result = $pop3->login($username, $password); + $pop3->disconnect(); + return $result; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/RADIUS.php b/includes/pear/Auth/Container/RADIUS.php new file mode 100644 index 0000000..585b641 --- /dev/null +++ b/includes/pear/Auth/Container/RADIUS.php @@ -0,0 +1,182 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against RADIUS servers + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Michael Bretterklieber <michael@bretterklieber.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: RADIUS.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.0 + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR Auth_RADIUS package + */ +require_once "Auth/RADIUS.php"; + +/** + * Storage driver for authenticating users against RADIUS servers. + * + * @category Authentication + * @package Auth + * @author Michael Bretterklieber <michael@bretterklieber.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.2.0 + */ +class Auth_Container_RADIUS extends Auth_Container +{ + + // {{{ properties + + /** + * Contains a RADIUS object + * @var object + */ + var $radius; + + /** + * Contains the authentication type + * @var string + */ + var $authtype; + + // }}} + // {{{ Auth_Container_RADIUS() [constructor] + + /** + * Constructor of the container class. + * + * $options can have these keys: + * 'servers' an array containing an array: servername, port, + * sharedsecret, timeout, maxtries + * 'configfile' The filename of the configuration file + * 'authtype' The type of authentication, one of: PAP, CHAP_MD5, + * MSCHAPv1, MSCHAPv2, default is PAP + * + * @param $options associative array + * @return object Returns an error object if something went wrong + */ + function Auth_Container_RADIUS($options) + { + $this->authtype = 'PAP'; + if (isset($options['authtype'])) { + $this->authtype = $options['authtype']; + } + $classname = 'Auth_RADIUS_' . $this->authtype; + if (!class_exists($classname)) { + PEAR::raiseError("Unknown Authtype, please use one of: " + ."PAP, CHAP_MD5, MSCHAPv1, MSCHAPv2!", 41, PEAR_ERROR_DIE); + } + + $this->radius = new $classname; + + if (isset($options['configfile'])) { + $this->radius->setConfigfile($options['configfile']); + } + + $servers = $options['servers']; + if (is_array($servers)) { + foreach ($servers as $server) { + $servername = $server[0]; + $port = isset($server[1]) ? $server[1] : 0; + $sharedsecret = isset($server[2]) ? $server[2] : 'testing123'; + $timeout = isset($server[3]) ? $server[3] : 3; + $maxtries = isset($server[4]) ? $server[4] : 3; + $this->radius->addServer($servername, $port, $sharedsecret, $timeout, $maxtries); + } + } + + if (!$this->radius->start()) { + PEAR::raiseError($this->radius->getError(), 41, PEAR_ERROR_DIE); + } + } + + // }}} + // {{{ fetchData() + + /** + * Authenticate + * + * @param string Username + * @param string Password + * @return bool true on success, false on reject + */ + function fetchData($username, $password, $challenge = null) + { + $this->log('Auth_Container_RADIUS::fetchData() called.', AUTH_LOG_DEBUG); + + switch($this->authtype) { + case 'CHAP_MD5': + case 'MSCHAPv1': + if (isset($challenge)) { + $this->radius->challenge = $challenge; + $this->radius->chapid = 1; + $this->radius->response = pack('H*', $password); + } else { + require_once 'Crypt/CHAP.php'; + $classname = 'Crypt_' . $this->authtype; + $crpt = new $classname; + $crpt->password = $password; + $this->radius->challenge = $crpt->challenge; + $this->radius->chapid = $crpt->chapid; + $this->radius->response = $crpt->challengeResponse(); + } + break; + + case 'MSCHAPv2': + require_once 'Crypt/CHAP.php'; + $crpt = new Crypt_MSCHAPv2; + $crpt->username = $username; + $crpt->password = $password; + $this->radius->challenge = $crpt->authChallenge; + $this->radius->peerChallenge = $crpt->peerChallenge; + $this->radius->chapid = $crpt->chapid; + $this->radius->response = $crpt->challengeResponse(); + break; + + default: + $this->radius->password = $password; + break; + } + + $this->radius->username = $username; + + $this->radius->putAuthAttributes(); + $result = $this->radius->send(); + if (PEAR::isError($result)) { + return false; + } + + $this->radius->getAttributes(); +// just for debugging +// $this->radius->dumpAttributes(); + + return $result; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/SAP.php b/includes/pear/Auth/Container/SAP.php new file mode 100644 index 0000000..58ce168 --- /dev/null +++ b/includes/pear/Auth/Container/SAP.php @@ -0,0 +1,179 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against a SAP system using the SAPRFC PHP extension. + * + * Requires the SAPRFC ext available at http://saprfc.sourceforge.net/ + * + * PHP version 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Stoyan Stefanov <ssttoo@gmail.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: SAP.php 302205 2010-08-14 14:08:08Z clockwerx $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.4.0 + */ + +/** + * Include Auth_Container base class + */ +require_once 'Auth/Container.php'; +/** + * Include PEAR for error handling + */ +require_once 'PEAR.php'; + +/** + * Performs authentication against a SAP system using the SAPRFC PHP extension. + * + * When the option GETSSO2 is TRUE (default) + * the Single Sign-On (SSO) ticket is retrieved + * and stored as an Auth attribute called 'sap' + * in order to be reused for consecutive connections. + * + * @category Authentication + * @package Auth + * @author Stoyan Stefanov <ssttoo@gmail.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 302205 $ + * @since Class available since Release 1.4.0 + */ +class Auth_Container_SAP extends Auth_Container { + + // {{{ properties + + /** + * @var array Default options + */ + var $options = array( + 'CLIENT' => '000', + 'LANG' => 'EN', + 'GETSSO2' => true, + ); + + // }}} + // {{{ Auth_Container_SAP() + + /** + * Class constructor. Checks that required options + * are present and that the SAPRFC extension is loaded + * + * Options that can be passed and their defaults: + * <pre> + * array( + * 'ASHOST' => "", + * 'SYSNR' => "", + * 'CLIENT' => "000", + * 'GWHOST' =>"", + * 'GWSERV' =>"", + * 'MSHOST' =>"", + * 'R3NAME' =>"", + * 'GROUP' =>"", + * 'LANG' =>"EN", + * 'TRACE' =>"", + * 'GETSSO2'=> true + * ) + * </pre> + * + * @param array array of options. + * @return void + */ + function Auth_Container_SAP($options) + { + $saprfc_loaded = PEAR::loadExtension('saprfc'); + if (!$saprfc_loaded) { + return PEAR::raiseError('Cannot use SAP authentication, ' + .'SAPRFC extension not loaded!'); + } + if (empty($options['R3NAME']) && empty($options['ASHOST'])) { + return PEAR::raiseError('R3NAME or ASHOST required for authentication'); + } + $this->options = array_merge($this->options, $options); + } + + // }}} + // {{{ fetchData() + + /** + * Performs username and password check + * + * @param string Username + * @param string Password + * @return boolean TRUE on success (valid user), FALSE otherwise + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_SAP::fetchData() called.', AUTH_LOG_DEBUG); + $connection_options = $this->options; + $connection_options['USER'] = $username; + $connection_options['PASSWD'] = $password; + $rfc = saprfc_open($connection_options); + if (!$rfc) { + $message = "Couldn't connect to the SAP system."; + $error = $this->getError(); + if ($error['message']) { + $message .= ': ' . $error['message']; + } + PEAR::raiseError($message, null, null, null, @$error['all']); + return false; + } else { + if (!empty($this->options['GETSSO2'])) { + $this->log('Attempting to retrieve SSO2 ticket.', AUTH_LOG_DEBUG); + if ($ticket = @saprfc_get_ticket($rfc)) { + $this->options['MYSAPSSO2'] = $ticket; + unset($this->options['GETSSO2']); + $this->_auth_obj->setAuthData('sap', $this->options); + } else { + PEAR::raiseError("SSO ticket retrieval failed"); + } + } + @saprfc_close($rfc); + return true; + } + + } + + // }}} + // {{{ getError() + + /** + * Retrieves the last error from the SAP connection + * and returns it as an array. + * + * @return array Array of error information + */ + function getError() + { + + $error = array(); + $sap_error = saprfc_error(); + if (empty($err)) { + return $error; + } + $err = explode("n", $sap_error); + foreach ($err AS $line) { + $item = explode(':', $line); + $error[strtolower(trim($item[0]))] = trim($item[1]); + } + $error['all'] = $sap_error; + return $error; + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Container/SMBPasswd.php b/includes/pear/Auth/Container/SMBPasswd.php new file mode 100644 index 0000000..15c3e75 --- /dev/null +++ b/includes/pear/Auth/Container/SMBPasswd.php @@ -0,0 +1,182 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against Samba password files + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Michael Bretterklieber <michael@bretterklieber.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: SMBPasswd.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.3 + */ + +/** + * Include PEAR File_SMBPasswd + */ +require_once "File/SMBPasswd.php"; +/** + * Include Auth_Container Base file + */ +require_once "Auth/Container.php"; +/** + * Include PEAR class for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for fetching login data from an SAMBA smbpasswd file. + * + * This storage container can handle SAMBA smbpasswd files. + * + * Example: + * $a = new Auth("SMBPasswd", '/usr/local/private/smbpasswd'); + * $a->start(); + * if ($a->getAuth()) { + * printf ("AUTH OK<br>\n"); + * $a->logout(); + * } + * + * @category Authentication + * @package Auth + * @author Michael Bretterklieber <michael@bretterklieber.com> + * @author Adam Ashley <aashley@php.net> + * @package Auth + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.2.3 + */ +class Auth_Container_SMBPasswd extends Auth_Container +{ + + // {{{ properties + + /** + * File_SMBPasswd object + * @var object + */ + var $pwfile; + + // }}} + + // {{{ Auth_Container_SMBPasswd() [constructor] + + /** + * Constructor of the container class + * + * @param $filename string filename for a passwd type file + * @return object Returns an error object if something went wrong + */ + function Auth_Container_SMBPasswd($filename) + { + $this->pwfile = new File_SMBPasswd($filename,0); + + if (!$this->pwfile->load()) { + PEAR::raiseError("Error while reading file contents.", 41, PEAR_ERROR_DIE); + return; + } + + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from pwfile + * + * @param string Username + * @param string Password + * @return boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_SMBPasswd::fetchData() called.', AUTH_LOG_DEBUG); + return $this->pwfile->verifyAccount($username, $password); + } + + // }}} + // {{{ listUsers() + + function listUsers() + { + $this->log('Auth_Container_SMBPasswd::fetchData() called.', AUTH_LOG_DEBUG); + return $this->pwfile->getAccounts(); + } + + // }}} + // {{{ addUser() + + /** + * Add a new user to the storage container + * + * @param string Username + * @param string Password + * @param array Additional information + * + * @return boolean + */ + function addUser($username, $password, $additional = '') + { + $this->log('Auth_Container_SMBPasswd::addUser() called.', AUTH_LOG_DEBUG); + $res = $this->pwfile->addUser($user, $additional['userid'], $pass); + if ($res === true) { + return $this->pwfile->save(); + } + return $res; + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @param string Username + */ + function removeUser($username) + { + $this->log('Auth_Container_SMBPasswd::removeUser() called.', AUTH_LOG_DEBUG); + $res = $this->pwfile->delUser($username); + if ($res === true) { + return $this->pwfile->save(); + } + return $res; + } + + // }}} + // {{{ changePassword() + + /** + * Change password for user in the storage container + * + * @param string Username + * @param string The new password + */ + function changePassword($username, $password) + { + $this->log('Auth_Container_SMBPasswd::changePassword() called.', AUTH_LOG_DEBUG); + $res = $this->pwfile->modUser($username, '', $password); + if ($res === true) { + return $this->pwfile->save(); + } + return $res; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/SOAP.php b/includes/pear/Auth/Container/SOAP.php new file mode 100644 index 0000000..2e11e6b --- /dev/null +++ b/includes/pear/Auth/Container/SOAP.php @@ -0,0 +1,229 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against a SOAP service + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Bruno Pedro <bpedro@co.sapo.pt> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: SOAP.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.0 + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR package for error handling + */ +require_once "PEAR.php"; +/** + * Include PEAR SOAP_Client + */ +require_once 'SOAP/Client.php'; + +/** + * Storage driver for fetching login data from SOAP + * + * This class takes one parameter (options), where + * you specify the following fields: endpoint, namespace, + * method, encoding, usernamefield and passwordfield. + * + * You can use specify features of your SOAP service + * by providing its parameters in an associative manner by + * using the '_features' array through the options parameter. + * + * The 'matchpassword' option should be set to false if your + * webservice doesn't return (username,password) pairs, but + * instead returns error when the login is invalid. + * + * Example usage: + * + * <?php + * + * ... + * + * $options = array ( + * 'endpoint' => 'http://your.soap.service/endpoint', + * 'namespace' => 'urn:/Your/Namespace', + * 'method' => 'get', + * 'encoding' => 'UTF-8', + * 'usernamefield' => 'login', + * 'passwordfield' => 'password', + * 'matchpasswords' => false, + * '_features' => array ( + * 'example_feature' => 'example_value', + * 'another_example' => '' + * ) + * ); + * $auth = new Auth('SOAP', $options, 'loginFunction'); + * $auth->start(); + * + * ... + * + * ?> + * + * @category Authentication + * @package Auth + * @author Bruno Pedro <bpedro@co.sapo.pt> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.2.0 + */ +class Auth_Container_SOAP extends Auth_Container +{ + + // {{{ properties + + /** + * Required options for the class + * @var array + * @access private + */ + var $_requiredOptions = array( + 'endpoint', + 'namespace', + 'method', + 'encoding', + 'usernamefield', + 'passwordfield', + ); + + /** + * Options for the class + * @var array + * @access private + */ + var $_options = array(); + + /** + * Optional SOAP features + * @var array + * @access private + */ + var $_features = array(); + + /** + * The SOAP response + * @var array + * @access public + */ + var $soapResponse = array(); + + /** + * The SOAP client + * @var mixed + * @access public + */ + var $soapClient = null; + + // }}} + // {{{ Auth_Container_SOAP() [constructor] + + /** + * Constructor of the container class + * + * @param $options, associative array with endpoint, namespace, method, + * usernamefield, passwordfield and optional features + */ + function Auth_Container_SOAP($options) + { + $this->_options = $options; + if (!isset($this->_options['matchpasswords'])) { + $this->_options['matchpasswords'] = true; + } + if (!empty($this->_options['_features'])) { + $this->_features = $this->_options['_features']; + unset($this->_options['_features']); + } + } + + // }}} + // {{{ fetchData() + + /** + * Fetch data from SOAP service + * + * Requests the SOAP service for the given username/password + * combination. + * + * @param string Username + * @param string Password + * @return mixed Returns the SOAP response or false if something went wrong + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_SOAP::fetchData() called.', AUTH_LOG_DEBUG); + // check if all required options are set + if (array_intersect($this->_requiredOptions, array_keys($this->_options)) != $this->_requiredOptions) { + return false; + } else { + // create a SOAP client and set encoding + $this->soapClient = new SOAP_Client($this->_options['endpoint']); + $this->soapClient->setEncoding($this->_options['encoding']); + } + + // set the trace option if requested + if (isset($this->_options['trace'])) { + $this->soapClient->__options['trace'] = true; + } + + // set the timeout option if requested + if (isset($this->_options['timeout'])) { + $this->soapClient->__options['timeout'] = $this->_options['timeout']; + } + + // assign username and password fields + $usernameField = new SOAP_Value($this->_options['usernamefield'],'string', $username); + $passwordField = new SOAP_Value($this->_options['passwordfield'],'string', $password); + $SOAPParams = array($usernameField, $passwordField); + + // assign optional features + foreach ($this->_features as $fieldName => $fieldValue) { + $SOAPParams[] = new SOAP_Value($fieldName, 'string', $fieldValue); + } + + // make SOAP call + $this->soapResponse = $this->soapClient->call( + $this->_options['method'], + $SOAPParams, + array('namespace' => $this->_options['namespace']) + ); + + if (!PEAR::isError($this->soapResponse)) { + if ($this->_options['matchpasswords']) { + // check if passwords match + if ($password == $this->soapResponse->{$this->_options['passwordfield']}) { + return true; + } else { + return false; + } + } else { + return true; + } + } else { + return false; + } + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/SOAP5.php b/includes/pear/Auth/Container/SOAP5.php new file mode 100644 index 0000000..698c685 --- /dev/null +++ b/includes/pear/Auth/Container/SOAP5.php @@ -0,0 +1,268 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against a SOAP service using PHP5 SoapClient + * + * PHP version 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Based upon Auth_Container_SOAP by Bruno Pedro <bpedro@co.sapo.pt> + * @author Marcel Oelke <puRe@rednoize.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: SOAP5.php 238999 2007-07-02 08:25:41Z aashley $ + * @since File available since Release 1.4.0 + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR package for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for fetching login data from SOAP using the PHP5 Builtin SOAP + * functions. This is a modification of the SOAP Storage driver from Bruno Pedro + * thats using the PEAR SOAP Package. + * + * This class takes one parameter (options), where + * you specify the following fields: + * * location and uri, or wsdl file + * * method to call on the SOAP service + * * usernamefield, the name of the parameter where the username is supplied + * * passwordfield, the name of the parameter where the password is supplied + * * matchpassword, whether to look for the password in the response from + * the function call or assume that no errors means user + * authenticated. + * + * See http://www.php.net/manual/en/ref.soap.php for further details + * on options for the PHP5 SoapClient which are passed through. + * + * Example usage without WSDL: + * + * <?php + * + * $options = array ( + * 'wsdl' => NULL, + * 'location' => 'http://your.soap.service/endpoint', + * 'uri' => 'urn:/Your/Namespace', + * 'method' => 'checkAuth', + * 'usernamefield' => 'username', + * 'passwordfield' => 'password', + * 'matchpasswords' => false, + * '_features' => array ( + * 'extra_parameter' => 'example_value', + * 'another_parameter' => 'foobar' + * ) + * ); + * + * $auth = new Auth('SOAP5', $options); + * $auth->start(); + * + * ?> + * + * Example usage with WSDL: + * + * <?php + * + * $options = array ( + * 'wsdl' => 'http://your.soap.service/wsdl', + * 'method' => 'checkAuth', + * 'usernamefield' => 'username', + * 'passwordfield' => 'password', + * 'matchpasswords' => false, + * '_features' => array ( + * 'extra_parameter' => 'example_value', + * 'another_parameter' => 'foobar' + * ) + * ); + * + * $auth = new Auth('SOAP5', $options); + * $auth->start(); + * + * ?> + * + * @category Authentication + * @package Auth + * @author Based upon Auth_Container_SOAP by Bruno Pedro <bpedro@co.sapo.pt> + * @author Marcel Oelke <puRe@rednoize.com> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 238999 $ + * @since Class available since Release 1.4.0 + */ +class Auth_Container_SOAP5 extends Auth_Container +{ + + // {{{ properties + + /** + * Required options for the class + * @var array + * @access private + */ + var $_requiredOptions = array( + 'location', + 'uri', + 'method', + 'usernamefield', + 'passwordfield', + 'wsdl', + ); + + /** + * Options for the class + * @var array + * @access private + */ + var $_options = array(); + + /** + * Optional SOAP features + * @var array + * @access private + */ + var $_features = array(); + + /** + * The SOAP response + * @var array + * @access public + */ + var $soapResponse = array(); + + // }}} + // {{{ Auth_Container_SOAP5() + + /** + * Constructor of the container class + * + * @param $options, associative array with endpoint, namespace, method, + * usernamefield, passwordfield and optional features + */ + function Auth_Container_SOAP5($options) + { + $this->_setDefaults(); + + foreach ($options as $name => $value) { + $this->_options[$name] = $value; + } + + if (!empty($this->_options['_features'])) { + $this->_features = $this->_options['_features']; + unset($this->_options['_features']); + } + } + + // }}} + // {{{ fetchData() + + /** + * Fetch data from SOAP service + * + * Requests the SOAP service for the given username/password + * combination. + * + * @param string Username + * @param string Password + * @return mixed Returns the SOAP response or false if something went wrong + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_SOAP5::fetchData() called.', AUTH_LOG_DEBUG); + $result = $this->_validateOptions(); + if (PEAR::isError($result)) + return $result; + + // create a SOAP client + $soapClient = new SoapClient($this->_options["wsdl"], $this->_options); + + $params = array(); + // first, assign the optional features + foreach ($this->_features as $fieldName => $fieldValue) { + $params[$fieldName] = $fieldValue; + } + // assign username and password ... + $params[$this->_options['usernamefield']] = $username; + $params[$this->_options['passwordfield']] = $password; + + try { + $this->soapResponse = $soapClient->__soapCall($this->_options['method'], $params); + + if ($this->_options['matchpasswords']) { + // check if passwords match + if ($password == $this->soapResponse[$this->_options['passwordfield']]) { + return true; + } else { + return false; + } + } else { + return true; + } + } catch (SoapFault $e) { + return PEAR::raiseError("Error retrieving authentication data. Received SOAP Fault: ".$e->faultstring, $e->faultcode); + } + } + + // }}} + // {{{ _validateOptions() + + /** + * Validate that the options passed to the container class are enough for us to proceed + * + * @access private + * @param array + */ + function _validateOptions() + { + if ( ( is_null($this->_options['wsdl']) + && is_null($this->_options['location']) + && is_null($this->_options['uri'])) + || ( is_null($this->_options['wsdl']) + && ( is_null($this->_options['location']) + || is_null($this->_options['uri'])))) { + return PEAR::raiseError('Either a WSDL file or a location/uri pair must be specified.'); + } + if (is_null($this->_options['method'])) { + return PEAR::raiseError('A method to call on the soap service must be specified.'); + } + return true; + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->_options['wsdl'] = null; + $this->_options['location'] = null; + $this->_options['uri'] = null; + $this->_options['method'] = null; + $this->_options['usernamefield'] = 'username'; + $this->_options['passwordfield'] = 'password'; + $this->_options['matchpasswords'] = true; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Container/vpopmail.php b/includes/pear/Auth/Container/vpopmail.php new file mode 100644 index 0000000..2350ca7 --- /dev/null +++ b/includes/pear/Auth/Container/vpopmail.php @@ -0,0 +1,88 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Storage driver for use against vpopmail setups + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Stanislav Grozev <tacho@orbitel.bg> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: vpopmail.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.2.0 + */ + +/** + * Include Auth_Container base class + */ +require_once "Auth/Container.php"; +/** + * Include PEAR package for error handling + */ +require_once "PEAR.php"; + +/** + * Storage driver for fetching login data from vpopmail + * + * @category Authentication + * @package Auth + * @author Stanislav Grozev <tacho@orbitel.bg> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.2.0 + */ +class Auth_Container_vpopmail extends Auth_Container { + + // {{{ Constructor + + /** + * Constructor of the container class + * + * @return void + */ + function Auth_Container_vpopmail() + { + if (!extension_loaded('vpopmail')) { + return PEAR::raiseError('Cannot use VPOPMail authentication, ' + .'VPOPMail extension not loaded!', 41, PEAR_ERROR_DIE); + } + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from vpopmail + * + * @param string Username - has to be valid email address + * @param string Password + * @return boolean + */ + function fetchData($username, $password) + { + $this->log('Auth_Container_vpopmail::fetchData() called.', AUTH_LOG_DEBUG); + $userdata = array(); + $userdata = preg_split("/@/", $username, 2); + $result = @vpopmail_auth_user($userdata[0], $userdata[1], $password); + + return $result; + } + + // }}} + +} +?> diff --git a/includes/pear/Auth/Controller.php b/includes/pear/Auth/Controller.php new file mode 100644 index 0000000..92d9f80 --- /dev/null +++ b/includes/pear/Auth/Controller.php @@ -0,0 +1,302 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Auth Controller + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Yavor Shahpasov <yavo@netsmart.com.cy> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Controller.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.3.0 + */ + +/** + * Controlls access to a group of php access + * and redirects to a predefined login page as + * needed + * + * In all pages + * <code> + * include_once('Auth.php'); + * include_once('Auth/Controller.php'); + * $_auth = new Auth('File', 'passwd'); + * $authController = new Auth_Controller($_auth, 'login.php', 'index.php'); + * $authController->start(); + * </code> + * + * In login.php + * <code> + * include_once('Auth.php'); + * include_once('Auth/Controller.php'); + * $_auth = new Auth('File', 'passwd'); + * $authController = new Auth_Controller($_auth, 'login.php', 'index.php'); + * $authController->start(); + * if( $authController->isAuthorised() ){ + * $authController->redirectBack(); + * } + * </code> + * + * @category Authentication + * @author Yavor Shahpasov <yavo@netsmart.com.cy> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.3.0 + */ +class Auth_Controller +{ + + // {{{ properties + + /** + * The Auth instance this controller is managing + * + * @var object Auth + */ + var $auth = null; + + /** + * The login URL + * @var string + * */ + var $login = null; + + /** + * The default index page to use when the caller page is not set + * + * @var string + */ + var $default = null; + + /** + * If this is set to true after a succesfull login the + * Auth_Controller::redirectBack() is invoked automatically + * + * @var boolean + */ + var $autoRedirectBack = false; + + // }}} + // {{{ Auth_Controller() [constructor] + + /** + * Constructor + * + * @param Auth An auth instance + * @param string The login page + * @param string The default page to go to if return page is not set + * @param array Some rules about which urls need to be sent to the login page + * @return void + * @todo Add a list of urls which need redirection + */ + function Auth_Controller(&$auth_obj, $login='login.php', $default='index.php', $accessList=array()) + { + $this->auth =& $auth_obj; + $this->_loginPage = $login; + $this->_defaultPage = $default; + @session_start(); + if (!empty($_GET['return']) && $_GET['return'] && !strstr($_GET['return'], $this->_loginPage)) { + $this->auth->setAuthData('returnUrl', $_GET['return']); + } + + if(!empty($_GET['authstatus']) && $this->auth->status == '') { + $this->auth->status = $_GET['authstatus']; + } + } + + // }}} + // {{{ setAutoRedirectBack() + + /** + * Enables auto redirection when login is done + * + * @param bool Sets the autoRedirectBack flag to this + * @see Auth_Controller::autoRedirectBack + * @return void + */ + function setAutoRedirectBack($flag = true) + { + $this->autoRedirectBack = $flag; + } + + // }}} + // {{{ redirectBack() + + /** + * Redirects Back to the calling page + * + * @return void + */ + function redirectBack() + { + // If redirectback go there + // else go to the default page + + $returnUrl = $this->auth->getAuthData('returnUrl'); + if(!$returnUrl) { + $returnUrl = $this->_defaultPage; + } + + // Add some entropy to the return to make it unique + // avoind problems with cached pages and proxies + if(strpos($returnUrl, '?') === false) { + $returnUrl .= '?'; + } + $returnUrl .= uniqid(''); + + // Track the auth status + if($this->auth->status != '') { + $url .= '&authstatus='.$this->auth->status; + } + header('Location:'.$returnUrl); + print("You could not be redirected to <a href=\"$returnUrl\">$returnUrl</a>"); + } + + // }}} + // {{{ redirectLogin() + + /** + * Redirects to the login Page if not authorised + * + * put return page on the query or in auth + * + * @return void + */ + function redirectLogin() + { + // Go to the login Page + + // For Auth, put some check to avoid infinite redirects, this should at least exclude + // the login page + + $url = $this->_loginPage; + if(strpos($url, '?') === false) { + $url .= '?'; + } + + if(!strstr($_SERVER['PHP_SELF'], $this->_loginPage)) { + $url .= 'return='.urlencode($_SERVER['PHP_SELF']); + } + + // Track the auth status + if($this->auth->status != '') { + $url .= '&authstatus='.$this->auth->status; + } + + header('Location:'.$url); + print("You could not be redirected to <a href=\"$url\">$url</a>"); + } + + // }}} + // {{{ start() + + /** + * Starts the Auth Procedure + * + * If the page requires login the user is redirected to the login page + * otherwise the Auth::start is called to initialize Auth + * + * @return void + * @todo Implement an access list which specifies which urls/pages need login and which do not + */ + function start() + { + // Check the accessList here + // ACL should be a list of urls with allow/deny + // If allow set allowLogin to false + // Some wild card matching should be implemented ?,* + if(!strstr($_SERVER['PHP_SELF'], $this->_loginPage) && !$this->auth->checkAuth()) { + $this->redirectLogin(); + } else { + $this->auth->start(); + // Logged on and on login page + if(strstr($_SERVER['PHP_SELF'], $this->_loginPage) && $this->auth->checkAuth()){ + $this->autoRedirectBack ? + $this->redirectBack() : + null ; + } + } + + + } + + // }}} + // {{{ isAuthorised() + + /** + * Checks is the user is logged on + * @see Auth::checkAuth() + */ + function isAuthorised() + { + return($this->auth->checkAuth()); + } + + // }}} + // {{{ checkAuth() + + /** + * Proxy call to auth + * @see Auth::checkAuth() + */ + function checkAuth() + { + return($this->auth->checkAuth()); + } + + // }}} + // {{{ logout() + + /** + * Proxy call to auth + * @see Auth::logout() + */ + function logout() + { + return($this->auth->logout()); + } + + // }}} + // {{{ getUsername() + + /** + * Proxy call to auth + * @see Auth::getUsername() + */ + function getUsername() + { + return($this->auth->getUsername()); + } + + // }}} + // {{{ getStatus() + + /** + * Proxy call to auth + * @see Auth::getStatus() + */ + function getStatus() + { + return($this->auth->getStatus()); + } + + // }}} + +} + +?> diff --git a/includes/pear/Auth/Frontend/Html.php b/includes/pear/Auth/Frontend/Html.php new file mode 100644 index 0000000..6719e92 --- /dev/null +++ b/includes/pear/Auth/Frontend/Html.php @@ -0,0 +1,142 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ + +/** + * Standard Html Login form + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Authentication + * @package Auth + * @author Martin Jansen <mj@php.net> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Html.php 237449 2007-06-12 03:11:27Z aashley $ + * @link http://pear.php.net/package/Auth + * @since File available since Release 1.3.0 + */ + +/** + * Standard Html Login form + * + * @category Authentication + * @package Auth + * @author Yavor Shahpasov <yavo@netsmart.com.cy> + * @author Adam Ashley <aashley@php.net> + * @copyright 2001-2006 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version Release: @package_version@ File: $Revision: 237449 $ + * @link http://pear.php.net/package/Auth + * @since Class available since Release 1.3.0 + */ +class Auth_Frontend_Html { + + // {{{ render() + + /** + * Displays the login form + * + * @param object The calling auth instance + * @param string The previously used username + * @return void + */ + function render(&$caller, $username = '') { + $loginOnClick = 'return true;'; + + // Try To Use Challene response + // TODO javascript might need some improvement for work on other browsers + if($caller->advancedsecurity && $caller->storage->supportsChallengeResponse() ) { + + // Init the secret cookie + $caller->session['loginchallenege'] = md5(microtime()); + + print "\n"; + print '<script language="JavaScript">'."\n"; + + include 'Auth/Frontend/md5.js'; + + print "\n"; + print ' function securePassword() { '."\n"; + print ' var pass = document.getElementById(\''.$caller->getPostPasswordField().'\');'."\n"; + print ' var secret = document.getElementById(\'authsecret\')'."\n"; + //print ' alert(pass);alert(secret); '."\n"; + + // If using md5 for password storage md5 the password before + // we hash it with the secret + // print ' alert(pass.value);'; + if ($caller->storage->getCryptType() == 'md5' ) { + print ' pass.value = hex_md5(pass.value); '."\n"; + #print ' alert(pass.value);'; + } + + print ' pass.value = hex_md5(pass.value+\''.$caller->session['loginchallenege'].'\'); '."\n"; + // print ' alert(pass.value);'; + print ' secret.value = 1;'."\n"; + print ' var doLogin = document.getElementById(\'doLogin\')'."\n"; + print ' doLogin.disabled = true;'."\n"; + print ' return true;'; + print ' } '."\n"; + print '</script>'."\n";; + print "\n"; + + $loginOnClick = ' return securePassword(); '; + } + + print '<center>'."\n"; + + $status = ''; + if (!empty($caller->status) && $caller->status == AUTH_EXPIRED) { + $status = '<i>Your session has expired. Please login again!</i>'."\n"; + } else if (!empty($caller->status) && $caller->status == AUTH_IDLED) { + $status = '<i>You have been idle for too long. Please login again!</i>'."\n"; + } else if (!empty ($caller->status) && $caller->status == AUTH_WRONG_LOGIN) { + $status = '<i>Wrong login data!</i>'."\n"; + } else if (!empty ($caller->status) && $caller->status == AUTH_SECURITY_BREACH) { + $status = '<i>Security problem detected. </i>'."\n"; + } + + print '<form method="post" action="'.$caller->server['PHP_SELF'].'" ' + .'onSubmit="'.$loginOnClick.'">'."\n"; + print '<table border="0" cellpadding="2" cellspacing="0" ' + .'summary="login form" align="center" >'."\n"; + print '<tr>'."\n"; + print ' <td colspan="2" bgcolor="#eeeeee"><strong>Login </strong>' + .$status.'</td>'."\n"; + print '</tr>'."\n"; + print '<tr>'."\n"; + print ' <td>Username:</td>'."\n"; + print ' <td><input type="text" id="'.$caller->getPostUsernameField() + .'" name="'.$caller->getPostUsernameField().'" value="' . $username + .'" /></td>'."\n"; + print '</tr>'."\n"; + print '<tr>'."\n"; + print ' <td>Password:</td>'."\n"; + print ' <td><input type="password" id="'.$caller->getPostPasswordField() + .'" name="'.$caller->getPostPasswordField().'" /></td>'."\n"; + print '</tr>'."\n"; + print '<tr>'."\n"; + + //onClick=" '.$loginOnClick.' " + print ' <td colspan="2" bgcolor="#eeeeee"><input value="Login" ' + .'id="doLogin" name="doLogin" type="submit" /></td>'."\n"; + print '</tr>'."\n"; + print '</table>'."\n"; + + // Might be a good idea to make the variable name variable + print '<input type="hidden" id="authsecret" name="authsecret" value="" />'; + print '</form>'."\n"; + print '</center>'."\n"; + } + + // }}} + +} + +?> diff --git a/includes/pear/Console/Getopt.php b/includes/pear/Console/Getopt.php new file mode 100644 index 0000000..eb65df2 --- /dev/null +++ b/includes/pear/Console/Getopt.php @@ -0,0 +1,360 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +/** + * PHP Version 5 + * + * Copyright (c) 1997-2004 The PHP Group + * + * This source file is subject to version 3.0 of the PHP license, + * that is bundled with this package in the file LICENSE, and is + * available through the world-wide-web at the following url: + * http://www.php.net/license/3_0.txt. + * If you did not receive a copy of the PHP license and are unable to + * obtain it through the world-wide-web, please send a note to + * license@php.net so we can mail you a copy immediately. + * + * @category Console + * @package Console_Getopt + * @author Andrei Zmievski <andrei@php.net> + * @license http://www.php.net/license/3_0.txt PHP 3.0 + * @version CVS: $Id: Getopt.php 306067 2010-12-08 00:13:31Z dufuz $ + * @link http://pear.php.net/package/Console_Getopt + */ + +require_once 'PEAR.php'; + +/** + * Command-line options parsing class. + * + * @category Console + * @package Console_Getopt + * @author Andrei Zmievski <andrei@php.net> + * @license http://www.php.net/license/3_0.txt PHP 3.0 + * @link http://pear.php.net/package/Console_Getopt + */ +class Console_Getopt +{ + + /** + * Parses the command-line options. + * + * The first parameter to this function should be the list of command-line + * arguments without the leading reference to the running program. + * + * The second parameter is a string of allowed short options. Each of the + * option letters can be followed by a colon ':' to specify that the option + * requires an argument, or a double colon '::' to specify that the option + * takes an optional argument. + * + * The third argument is an optional array of allowed long options. The + * leading '--' should not be included in the option name. Options that + * require an argument should be followed by '=', and options that take an + * option argument should be followed by '=='. + * + * The return value is an array of two elements: the list of parsed + * options and the list of non-option command-line arguments. Each entry in + * the list of parsed options is a pair of elements - the first one + * specifies the option, and the second one specifies the option argument, + * if there was one. + * + * Long and short options can be mixed. + * + * Most of the semantics of this function are based on GNU getopt_long(). + * + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option + * + * @return array two-element array containing the list of parsed options and + * the non-option arguments + */ + public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false) + { + return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown); + } + + /** + * This function expects $args to start with the script name (POSIX-style). + * Preserved for backwards compatibility. + * + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * + * @see getopt2() + * @return array two-element array containing the list of parsed options and + * the non-option arguments + */ + public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false) + { + return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown); + } + + /** + * The actual implementation of the argument parsing code. + * + * @param int $version Version to use + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option + * + * @return array + */ + public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false) + { + // in case you pass directly readPHPArgv() as the first arg + if (PEAR::isError($args)) { + return $args; + } + + if (empty($args)) { + return array(array(), array()); + } + + $non_opts = $opts = array(); + + settype($args, 'array'); + + if ($long_options) { + sort($long_options); + } + + /* + * Preserve backwards compatibility with callers that relied on + * erroneous POSIX fix. + */ + if ($version < 2) { + if (isset($args[0]{0}) && $args[0]{0} != '-') { + array_shift($args); + } + } + + reset($args); + while (list($i, $arg) = each($args)) { + /* The special element '--' means explicit end of + options. Treat the rest of the arguments as non-options + and end the loop. */ + if ($arg == '--') { + $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); + break; + } + + if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) { + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } elseif (strlen($arg) > 1 && $arg{1} == '-') { + $error = Console_Getopt::_parseLongOption(substr($arg, 2), + $long_options, + $opts, + $args, + $skip_unknown); + if (PEAR::isError($error)) { + return $error; + } + } elseif ($arg == '-') { + // - is stdin + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } else { + $error = Console_Getopt::_parseShortOption(substr($arg, 1), + $short_options, + $opts, + $args, + $skip_unknown); + if (PEAR::isError($error)) { + return $error; + } + } + } + + return array($opts, $non_opts); + } + + /** + * Parse short option + * + * @param string $arg Argument + * @param string[] $short_options Available short options + * @param string[][] &$opts + * @param string[] &$args + * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option + * + * @return void + */ + public static function _parseShortOption($arg, $short_options, &$opts, &$args, $skip_unknown) + { + for ($i = 0; $i < strlen($arg); $i++) { + $opt = $arg{$i}; + $opt_arg = null; + + /* Try to find the short option in the specifier string. */ + if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') { + if ($skip_unknown === true) { + break; + } + + $msg = "Console_Getopt: unrecognized option -- $opt"; + return PEAR::raiseError($msg); + } + + if (strlen($spec) > 1 && $spec{1} == ':') { + if (strlen($spec) > 2 && $spec{2} == ':') { + if ($i + 1 < strlen($arg)) { + /* Option takes an optional argument. Use the remainder of + the arg string if there is anything left. */ + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } + } else { + /* Option requires an argument. Use the remainder of the arg + string if there is anything left. */ + if ($i + 1 < strlen($arg)) { + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } else if (list(, $opt_arg) = each($args)) { + /* Else use the next argument. */; + if (Console_Getopt::_isShortOpt($opt_arg) + || Console_Getopt::_isLongOpt($opt_arg)) { + $msg = "option requires an argument --$opt"; + return PEAR::raiseError("Console_Getopt:" . $msg); + } + } else { + $msg = "option requires an argument --$opt"; + return PEAR::raiseError("Console_Getopt:" . $msg); + } + } + } + + $opts[] = array($opt, $opt_arg); + } + } + + /** + * Checks if an argument is a short option + * + * @param string $arg Argument to check + * + * @return bool + */ + private static function _isShortOpt($arg) + { + return strlen($arg) == 2 && $arg[0] == '-' + && preg_match('/[a-zA-Z]/', $arg[1]); + } + + /** + * Checks if an argument is a long option + * + * @param string $arg Argument to check + * + * @return bool + */ + private static function _isLongOpt($arg) + { + return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' && + preg_match('/[a-zA-Z]+$/', substr($arg, 2)); + } + + /** + * Parse long option + * + * @param string $arg Argument + * @param string[] $long_options Available long options + * @param string[][] &$opts + * @param string[] &$args + * + * @return void|PEAR_Error + */ + private static function _parseLongOption($arg, $long_options, &$opts, &$args, $skip_unknown) + { + @list($opt, $opt_arg) = explode('=', $arg, 2); + + $opt_len = strlen($opt); + + for ($i = 0; $i < count($long_options); $i++) { + $long_opt = $long_options[$i]; + $opt_start = substr($long_opt, 0, $opt_len); + + $long_opt_name = str_replace('=', '', $long_opt); + + /* Option doesn't match. Go on to the next one. */ + if ($long_opt_name != $opt) { + continue; + } + + $opt_rest = substr($long_opt, $opt_len); + + /* Check that the options uniquely matches one of the allowed + options. */ + if ($i + 1 < count($long_options)) { + $next_option_rest = substr($long_options[$i + 1], $opt_len); + } else { + $next_option_rest = ''; + } + + if ($opt_rest != '' && $opt{0} != '=' && + $i + 1 < count($long_options) && + $opt == substr($long_options[$i+1], 0, $opt_len) && + $next_option_rest != '' && + $next_option_rest{0} != '=') { + + $msg = "Console_Getopt: option --$opt is ambiguous"; + return PEAR::raiseError($msg); + } + + if (substr($long_opt, -1) == '=') { + if (substr($long_opt, -2) != '==') { + /* Long option requires an argument. + Take the next argument if one wasn't specified. */; + if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) { + $msg = "Console_Getopt: option requires an argument --$opt"; + return PEAR::raiseError($msg); + } + + if (Console_Getopt::_isShortOpt($opt_arg) + || Console_Getopt::_isLongOpt($opt_arg)) { + $msg = "Console_Getopt: option requires an argument --$opt"; + return PEAR::raiseError($msg); + } + } + } else if ($opt_arg) { + $msg = "Console_Getopt: option --$opt doesn't allow an argument"; + return PEAR::raiseError($msg); + } + + $opts[] = array('--' . $opt, $opt_arg); + return; + } + + if ($skip_unknown === true) { + return; + } + + return PEAR::raiseError("Console_Getopt: unrecognized option --$opt"); + } + + /** + * Safely read the $argv PHP array across different PHP configurations. + * Will take care on register_globals and register_argc_argv ini directives + * + * @return mixed the $argv PHP array or PEAR error if not registered + */ + public static function readPHPArgv() + { + global $argv; + if (!is_array($argv)) { + if (!@is_array($_SERVER['argv'])) { + if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { + $msg = "Could not read cmd args (register_argc_argv=Off?)"; + return PEAR::raiseError("Console_Getopt: " . $msg); + } + return $GLOBALS['HTTP_SERVER_VARS']['argv']; + } + return $_SERVER['argv']; + } + return $argv; + } + +} diff --git a/includes/pear/HTTP.php b/includes/pear/HTTP.php new file mode 100644 index 0000000..6ebd6db --- /dev/null +++ b/includes/pear/HTTP.php @@ -0,0 +1,548 @@ +<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * HTTP
+ *
+ * PHP versions 4 and 5
+ *
+ * @category HTTP
+ * @package HTTP
+ * @author Stig Bakken <ssb@fast.no>
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Richard Heyes <richard@php.net>
+ * @author Philippe Jausions <jausions@php.net>
+ * @author Michael Wallner <mike@php.net>
+ * @copyright 2002-2008 The Authors
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: HTTP.php,v 1.56 2008/08/31 20:15:43 jausions Exp $
+ * @link http://pear.php.net/package/HTTP
+ */
+
+/**
+ * Miscellaneous HTTP Utilities
+ *
+ * PEAR::HTTP provides static shorthand methods for generating HTTP dates,
+ * issueing HTTP HEAD requests, building absolute URIs, firing redirects and
+ * negotiating user preferred language.
+ *
+ * @category HTTP
+ * @package HTTP
+ * @author Stig Bakken <ssb@fast.no>
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Richard Heyes <richard@php.net>
+ * @author Philippe Jausions <jausions@php.net>
+ * @author Michael Wallner <mike@php.net>
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @abstract
+ * @version Release: $Revision: 1.56 $
+ * @link http://pear.php.net/package/HTTP
+ */
+class HTTP
+{
+ /**
+ * Formats a RFC compliant GMT date HTTP header. This function honors the
+ * "y2k_compliance" php.ini directive and formats the GMT date corresponding
+ * to either RFC850 or RFC822.
+ *
+ * @param mixed $time unix timestamp or date (default = current time)
+ *
+ * @return mixed GMT date string, or false for an invalid $time parameter
+ * @access public
+ * @static
+ */
+ function Date($time = null)
+ {
+ if (!isset($time)) {
+ $time = time();
+ } elseif (!is_numeric($time) && (-1 === $time = strtotime($time))) {
+ return false;
+ }
+
+ // RFC822 or RFC850
+ $format = ini_get('y2k_compliance') ? 'D, d M Y' : 'l, d-M-y';
+
+ return gmdate($format .' H:i:s \G\M\T', $time);
+ }
+
+ /**
+ * Negotiates language with the user's browser through the Accept-Language
+ * HTTP header or the user's host address. Language codes are generally in
+ * the form "ll" for a language spoken in only one country, or "ll-CC" for a
+ * language spoken in a particular country. For example, U.S. English is
+ * "en-US", while British English is "en-UK". Portugese as spoken in
+ * Portugal is "pt-PT", while Brazilian Portugese is "pt-BR".
+ *
+ * Quality factors in the Accept-Language: header are supported, e.g.:
+ * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8
+ *
+ * <code>
+ * require_once 'HTTP.php';
+ * $langs = array(
+ * 'en' => 'locales/en',
+ * 'en-US' => 'locales/en',
+ * 'en-UK' => 'locales/en',
+ * 'de' => 'locales/de',
+ * 'de-DE' => 'locales/de',
+ * 'de-AT' => 'locales/de',
+ * );
+ * $neg = HTTP::negotiateLanguage($langs);
+ * $dir = $langs[$neg];
+ * </code>
+ *
+ * @param array $supported An associative array of supported languages,
+ * whose values must evaluate to true.
+ * @param string $default The default language to use if none is found.
+ *
+ * @return string The negotiated language result or the supplied default.
+ * @static
+ * @access public
+ */
+ function negotiateLanguage($supported, $default = 'en-US')
+ {
+ $supp = array();
+ foreach ($supported as $lang => $isSupported) {
+ if ($isSupported) {
+ $supp[strtolower($lang)] = $lang;
+ }
+ }
+
+ if (!count($supp)) {
+ return $default;
+ }
+
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_LANGUAGE'],
+ $supp);
+ if (!is_null($match)) {
+ return $match;
+ }
+ }
+
+ if (isset($_SERVER['REMOTE_HOST'])) {
+ $lang = strtolower(end($h = explode('.', $_SERVER['REMOTE_HOST'])));
+ if (isset($supp[$lang])) {
+ return $supp[$lang];
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Negotiates charset with the user's browser through the Accept-Charset
+ * HTTP header.
+ *
+ * Quality factors in the Accept-Charset: header are supported, e.g.:
+ * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8
+ *
+ * <code>
+ * require_once 'HTTP.php';
+ * $charsets = array(
+ * 'UTF-8',
+ * 'ISO-8859-1',
+ * );
+ * $charset = HTTP::negotiateCharset($charsets);
+ * </code>
+ *
+ * @param array $supported An array of supported charsets
+ * @param string $default The default charset to use if none is found.
+ *
+ * @return string The negotiated language result or the supplied default.
+ * @static
+ * @author Philippe Jausions <jausions@php.net>
+ * @access public
+ * @since 1.4.1
+ */
+ function negotiateCharset($supported, $default = 'ISO-8859-1')
+ {
+ $supp = array();
+ foreach ($supported as $charset) {
+ $supp[strtolower($charset)] = $charset;
+ }
+
+ if (!count($supp)) {
+ return $default;
+ }
+
+ if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
+ $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_CHARSET'],
+ $supp);
+ if (!is_null($match)) {
+ return $match;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Negotiates content type with the user's browser through the Accept
+ * HTTP header.
+ *
+ * Quality factors in the Accept: header are supported, e.g.:
+ * Accept: application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8
+ *
+ * <code>
+ * require_once 'HTTP.php';
+ * $contentType = array(
+ * 'application/xhtml+xml',
+ * 'application/xml',
+ * 'text/html',
+ * 'text/plain',
+ * );
+ * $mime = HTTP::negotiateContentType($contentType);
+ * </code>
+ *
+ * @param array $supported An associative array of supported MIME types.
+ * @param string $default The default type to use if none match.
+ *
+ * @return string The negotiated MIME type result or the supplied default.
+ * @static
+ * @author Philippe Jausions <jausions@php.net>
+ * @access public
+ * @since 1.4.1
+ */
+ function negotiateMimeType($supported, $default)
+ {
+ $supp = array();
+ foreach ($supported as $type) {
+ $supp[strtolower($type)] = $type;
+ }
+
+ if (!count($supp)) {
+ return $default;
+ }
+
+ if (isset($_SERVER['HTTP_ACCEPT'])) {
+ $accepts = HTTP::_sortAccept($_SERVER['HTTP_ACCEPT']);
+
+ foreach ($accepts as $type => $q) {
+ if (substr($type, -2) != '/*') {
+ if (isset($supp[$type])) {
+ return $supp[$type];
+ }
+ continue;
+ }
+ if ($type == '*/*') {
+ return array_shift($supp);
+ }
+ list($general, $specific) = explode('/', $type);
+ $general .= '/';
+ $len = strlen($general);
+ foreach ($supp as $mime => $t) {
+ if (strncasecmp($general, $mime, $len) == 0) {
+ return $t;
+ }
+ }
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Parses a weighed "Accept" HTTP header and matches it against a list
+ * of supported options
+ *
+ * @param string $header The HTTP "Accept" header to parse
+ * @param array $supported A list of supported values
+ *
+ * @return string|NULL a matched option, or NULL if no match
+ * @access private
+ * @static
+ */
+ function _matchAccept($header, $supported)
+ {
+ $matches = HTTP::_sortAccept($header);
+ foreach ($matches as $key => $q) {
+ if (isset($supported[$key])) {
+ return $supported[$key];
+ }
+ }
+ // If any (i.e. "*") is acceptable, return the first supported format
+ if (isset($matches['*'])) {
+ return array_shift($supported);
+ }
+ return null;
+ }
+
+ /**
+ * Parses and sorts a weighed "Accept" HTTP header
+ *
+ * @param string $header The HTTP "Accept" header to parse
+ *
+ * @return array a sorted list of "accept" options
+ * @access private
+ * @static
+ */
+ function _sortAccept($header)
+ {
+ $matches = array();
+ foreach (explode(',', $header) as $option) {
+ $option = array_map('trim', explode(';', $option));
+
+ $l = strtolower($option[0]);
+ if (isset($option[1])) {
+ $q = (float) str_replace('q=', '', $option[1]);
+ } else {
+ $q = null;
+ // Assign default low weight for generic values
+ if ($l == '*/*') {
+ $q = 0.01;
+ } elseif (substr($l, -1) == '*') {
+ $q = 0.02;
+ }
+ }
+ // Unweighted values, get high weight by their position in the
+ // list
+ $matches[$l] = isset($q) ? $q : 1000 - count($matches);
+ }
+ arsort($matches, SORT_NUMERIC);
+ return $matches;
+ }
+
+ /**
+ * Sends a "HEAD" HTTP command to a server and returns the headers
+ * as an associative array.
+ *
+ * Example output could be:
+ * <code>
+ * Array
+ * (
+ * [response_code] => 200 // The HTTP response code
+ * [response] => HTTP/1.1 200 OK // The full HTTP response string
+ * [Date] => Fri, 11 Jan 2002 01:41:44 GMT
+ * [Server] => Apache/1.3.20 (Unix) PHP/4.1.1
+ * [X-Powered-By] => PHP/4.1.1
+ * [Connection] => close
+ * [Content-Type] => text/html
+ * )
+ * </code>
+ *
+ * @param string $url A valid URL, e.g.: http://pear.php.net/credits.php
+ * @param integer $timeout Timeout in seconds (default = 10)
+ *
+ * @return array Returns associative array of response headers on success
+ * or PEAR error on failure.
+ * @static
+ * @access public
+ * @see HTTP_Client::head()
+ * @see HTTP_Request
+ */
+ function head($url, $timeout = 10)
+ {
+ $p = parse_url($url);
+ if (!isset($p['scheme'])) {
+ $p = parse_url(HTTP::absoluteURI($url));
+ } elseif ($p['scheme'] != 'http') {
+ return HTTP::raiseError('Unsupported protocol: '. $p['scheme']);
+ }
+
+ $port = isset($p['port']) ? $p['port'] : 80;
+
+ if (!$fp = @fsockopen($p['host'], $port, $eno, $estr, $timeout)) {
+ return HTTP::raiseError("Connection error: $estr ($eno)");
+ }
+
+ $path = !empty($p['path']) ? $p['path'] : '/';
+ $path .= !empty($p['query']) ? '?' . $p['query'] : '';
+
+ fputs($fp, "HEAD $path HTTP/1.0\r\n");
+ fputs($fp, 'Host: ' . $p['host'] . ':' . $port . "\r\n");
+ fputs($fp, "Connection: close\r\n\r\n");
+
+ $response = rtrim(fgets($fp, 4096));
+ if (preg_match("|^HTTP/[^\s]*\s(.*?)\s|", $response, $status)) {
+ $headers['response_code'] = $status[1];
+ }
+ $headers['response'] = $response;
+
+ while ($line = fgets($fp, 4096)) {
+ if (!trim($line)) {
+ break;
+ }
+ if (($pos = strpos($line, ':')) !== false) {
+ $header = substr($line, 0, $pos);
+ $value = trim(substr($line, $pos + 1));
+
+ $headers[$header] = $value;
+ }
+ }
+ fclose($fp);
+ return $headers;
+ }
+
+ /**
+ * This function redirects the client. This is done by issuing
+ * a "Location" header and exiting if wanted. If you set $rfc2616 to true
+ * HTTP will output a hypertext note with the location of the redirect.
+ *
+ * @param string $url URL where the redirect should go to.
+ * @param bool $exit Whether to exit immediately after redirection.
+ * @param bool $rfc2616 Wheter to output a hypertext note where we're
+ * redirecting to (Redirecting to
+ * <a href="...">...</a>.)
+ *
+ * @return boolean Returns TRUE on succes (or exits) or FALSE if headers
+ * have already been sent.
+ * @static
+ * @access public
+ */
+ function redirect($url, $exit = true, $rfc2616 = false)
+ {
+ if (headers_sent()) {
+ return false;
+ }
+
+ $url = HTTP::absoluteURI($url);
+ header('Location: '. $url);
+
+ if ($rfc2616 && isset($_SERVER['REQUEST_METHOD'])
+ && $_SERVER['REQUEST_METHOD'] != 'HEAD') {
+ echo '
+<p>Redirecting to: <a href="'.str_replace('"', '%22', $url).'">'
+ .htmlspecialchars($url).'</a>.</p>
+<script type="text/javascript">
+//<![CDATA[
+if (location.replace == null) {
+ location.replace = location.assign;
+}
+location.replace("'.str_replace('"', '\\"', $url).'");
+// ]]>
+</script>';
+ }
+ if ($exit) {
+ exit;
+ }
+ return true;
+ }
+
+ /**
+ * This function returns the absolute URI for the partial URL passed.
+ * The current scheme (HTTP/HTTPS), host server, port, current script
+ * location are used if necessary to resolve any relative URLs.
+ *
+ * Offsets potentially created by PATH_INFO are taken care of to resolve
+ * relative URLs to the current script.
+ *
+ * You can choose a new protocol while resolving the URI. This is
+ * particularly useful when redirecting a web browser using relative URIs
+ * and to switch from HTTP to HTTPS, or vice-versa, at the same time.
+ *
+ * @param string $url Absolute or relative URI the redirect should
+ * go to.
+ * @param string $protocol Protocol to use when redirecting URIs.
+ * @param integer $port A new port number.
+ *
+ * @return string The absolute URI.
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @static
+ * @access public
+ */
+ function absoluteURI($url = null, $protocol = null, $port = null)
+ {
+ // filter CR/LF
+ $url = str_replace(array("\r", "\n"), ' ', $url);
+
+ // Mess around protocol and port with already absolute URIs
+ if (preg_match('!^([a-z0-9]+)://!i', $url)) {
+ if (empty($protocol) && empty($port)) {
+ return $url;
+ }
+ if (!empty($protocol)) {
+ $url = $protocol .':'. end($array = explode(':', $url, 2));
+ }
+ if (!empty($port)) {
+ $url = preg_replace('!^(([a-z0-9]+)://[^/:]+)(:[\d]+)?!i',
+ '\1:'. $port, $url);
+ }
+ return $url;
+ }
+
+ $host = 'localhost';
+ if (!empty($_SERVER['HTTP_HOST'])) {
+ list($host) = explode(':', $_SERVER['HTTP_HOST']);
+ } elseif (!empty($_SERVER['SERVER_NAME'])) {
+ list($host) = explode(':', $_SERVER['SERVER_NAME']);
+ }
+
+ if (empty($protocol)) {
+ if (isset($_SERVER['HTTPS']) && !strcasecmp($_SERVER['HTTPS'], 'on')) {
+ $protocol = 'https';
+ } else {
+ $protocol = 'http';
+ }
+ if (!isset($port) || $port != intval($port)) {
+ $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
+ }
+ }
+
+ if ($protocol == 'http' && $port == 80) {
+ unset($port);
+ }
+ if ($protocol == 'https' && $port == 443) {
+ unset($port);
+ }
+
+ $server = $protocol.'://'.$host.(isset($port) ? ':'.$port : '');
+
+ $uriAll = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI']
+ : $_SERVER['PHP_SELF'];
+ if (false !== ($q = strpos($uriAll, '?'))) {
+ $uriBase = substr($uriAll, 0, $q);
+ } else {
+ $uriBase = $uriAll;
+ }
+ if (!strlen($url) || $url{0} == '#') {
+ $url = $uriAll.$url;
+ } elseif ($url{0} == '?') {
+ $url = $uriBase.$url;
+ }
+ if ($url{0} == '/') {
+ return $server . $url;
+ }
+
+ // Adjust for PATH_INFO if needed
+ if (isset($_SERVER['PATH_INFO']) && strlen($_SERVER['PATH_INFO'])) {
+ $path = dirname(substr($uriBase, 0,
+ -strlen($_SERVER['PATH_INFO'])));
+ } else {
+ /**
+ * Fixes bug #12672 PHP_SELF ending on / causes incorrect redirects
+ *
+ * @link http://pear.php.net/bugs/12672
+ */
+ $path = dirname($uriBase.'-');
+ }
+
+ if (substr($path = strtr($path, '\\', '/'), -1) != '/') {
+ $path .= '/';
+ }
+
+ return $server . $path . $url;
+ }
+
+ /**
+ * Raise Error
+ *
+ * Lazy raising of PEAR_Errors.
+ *
+ * @param mixed $error Error
+ * @param integer $code Error code
+ *
+ * @return object PEAR_Error
+ * @static
+ * @access protected
+ */
+ function raiseError($error = null, $code = null)
+ {
+ include_once 'PEAR.php';
+ return PEAR::raiseError($error, $code);
+ }
+}
+
+?>
\ No newline at end of file diff --git a/includes/pear/HTTP/Download.php b/includes/pear/HTTP/Download.php new file mode 100644 index 0000000..8cfc348 --- /dev/null +++ b/includes/pear/HTTP/Download.php @@ -0,0 +1,1243 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * HTTP::Download + * + * PHP versions 4 and 5 + * + * @category HTTP + * @package HTTP_Download + * @author Michael Wallner <mike@php.net> + * @copyright 2003-2005 Michael Wallner + * @license BSD, revised + * @version CVS: $Id: Download.php 304423 2010-10-15 13:36:46Z clockwerx $ + * @link http://pear.php.net/package/HTTP_Download + */ + +// {{{ includes +/** + * Requires PEAR + */ +require_once 'PEAR.php'; + +/** + * Requires HTTP_Header + */ +require_once 'HTTP/Header.php'; +// }}} + +// {{{ constants +/**#@+ Use with HTTP_Download::setContentDisposition() **/ +/** + * Send data as attachment + */ +define('HTTP_DOWNLOAD_ATTACHMENT', 'attachment'); +/** + * Send data inline + */ +define('HTTP_DOWNLOAD_INLINE', 'inline'); +/**#@-**/ + +/**#@+ Use with HTTP_Download::sendArchive() **/ +/** + * Send as uncompressed tar archive + */ +define('HTTP_DOWNLOAD_TAR', 'TAR'); +/** + * Send as gzipped tar archive + */ +define('HTTP_DOWNLOAD_TGZ', 'TGZ'); +/** + * Send as bzip2 compressed tar archive + */ +define('HTTP_DOWNLOAD_BZ2', 'BZ2'); +/** + * Send as zip archive + */ +define('HTTP_DOWNLOAD_ZIP', 'ZIP'); +/**#@-**/ + +/**#@+ + * Error constants + */ +define('HTTP_DOWNLOAD_E_HEADERS_SENT', -1); +define('HTTP_DOWNLOAD_E_NO_EXT_ZLIB', -2); +define('HTTP_DOWNLOAD_E_NO_EXT_MMAGIC', -3); +define('HTTP_DOWNLOAD_E_INVALID_FILE', -4); +define('HTTP_DOWNLOAD_E_INVALID_PARAM', -5); +define('HTTP_DOWNLOAD_E_INVALID_RESOURCE', -6); +define('HTTP_DOWNLOAD_E_INVALID_REQUEST', -7); +define('HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE', -8); +define('HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE', -9); +/**#@-**/ +// }}} + +/** + * Send HTTP Downloads/Responses. + * + * With this package you can handle (hidden) downloads. + * It supports partial downloads, resuming and sending + * raw data ie. from database BLOBs. + * + * <i>ATTENTION:</i> + * You shouldn't use this package together with ob_gzhandler or + * zlib.output_compression enabled in your php.ini, especially + * if you want to send already gzipped data! + * + * @access public + * @version $Revision: 304423 $ + */ +class HTTP_Download +{ + // {{{ protected member variables + /** + * Path to file for download + * + * @see HTTP_Download::setFile() + * @access protected + * @var string + */ + var $file = ''; + + /** + * Data for download + * + * @see HTTP_Download::setData() + * @access protected + * @var string + */ + var $data = null; + + /** + * Resource handle for download + * + * @see HTTP_Download::setResource() + * @access protected + * @var int + */ + var $handle = null; + + /** + * Whether to gzip the download + * + * @access protected + * @var bool + */ + var $gzip = false; + + /** + * Whether to allow caching of the download on the clients side + * + * @access protected + * @var bool + */ + var $cache = true; + + /** + * Size of download + * + * @access protected + * @var int + */ + var $size = 0; + + /** + * Last modified + * + * @access protected + * @var int + */ + var $lastModified = 0; + + /** + * HTTP headers + * + * @access protected + * @var array + */ + var $headers = array( + 'Content-Type' => 'application/x-octetstream', + 'Pragma' => 'cache', + 'Cache-Control' => 'public, must-revalidate, max-age=0', + 'Accept-Ranges' => 'bytes', + 'X-Sent-By' => 'PEAR::HTTP::Download' + ); + + /** + * HTTP_Header + * + * @access protected + * @var object + */ + var $HTTP = null; + + /** + * ETag + * + * @access protected + * @var string + */ + var $etag = ''; + + /** + * Buffer Size + * + * @access protected + * @var int + */ + var $bufferSize = 2097152; + + /** + * Throttle Delay + * + * @access protected + * @var float + */ + var $throttleDelay = 0; + + /** + * Sent Bytes + * + * @access public + * @var int + */ + var $sentBytes = 0; + + /** + * Startup error + * + * @var PEAR_Error + * @access protected + */ + var $_error = null; + // }}} + + // {{{ constructor + /** + * Constructor + * + * Set supplied parameters. + * + * @access public + * @param array $params associative array of parameters + * <strong>one of:</strong> + * <ul> + * <li>'file' => path to file for download</li> + * <li>'data' => raw data for download</li> + * <li>'resource' => resource handle for download</li> + * </ul> + * <strong>and any of:</strong> + * <ul> + * <li>'cache' => whether to allow cs caching</li> + * <li>'gzip' => whether to gzip the download</li> + * <li>'lastmodified' => unix timestamp</li> + * <li>'contenttype' => content type of download</li> + * <li>'contentdisposition' => content disposition</li> + * <li>'buffersize' => amount of bytes to buffer</li> + * <li>'throttledelay' => amount of secs to sleep</li> + * <li>'cachecontrol' => cache privacy and validity</li> + * </ul> + * + * 'Content-Disposition' is not HTTP compliant, but most browsers + * follow this header, so it was borrowed from MIME standard. + * + * It looks like this: + * "Content-Disposition: attachment; filename=example.tgz". + * + * @see HTTP_Download::setContentDisposition() + */ + function HTTP_Download($params = array()) + { + $this->HTTP = &new HTTP_Header; + $this->_error = $this->setParams($params); + } + // }}} + + // {{{ public methods + /** + * Set parameters + * + * Set supplied parameters through its accessor methods. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $params associative array of parameters + * + * @see HTTP_Download::HTTP_Download() + */ + function setParams($params) + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + foreach((array) $params as $param => $value){ + $method = 'set'. $param; + + if (!method_exists($this, $method)) { + return PEAR::raiseError( + "Method '$method' doesn't exist.", + HTTP_DOWNLOAD_E_INVALID_PARAM + ); + } + + $e = call_user_func_array(array(&$this, $method), (array) $value); + + if (PEAR::isError($e)) { + return $e; + } + } + return true; + } + + /** + * Set path to file for download + * + * The Last-Modified header will be set to files filemtime(), actually. + * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_FILE) if file doesn't exist. + * Sends HTTP 404 or 403 status if $send_error is set to true. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $file path to file for download + * @param bool $send_error whether to send HTTP/404 or 403 if + * the file wasn't found or is not readable + */ + function setFile($file, $send_error = true) + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + $file = realpath($file); + if (!is_file($file)) { + if ($send_error) { + $this->HTTP->sendStatusCode(404); + } + return PEAR::raiseError( + "File '$file' not found.", + HTTP_DOWNLOAD_E_INVALID_FILE + ); + } + if (!is_readable($file)) { + if ($send_error) { + $this->HTTP->sendStatusCode(403); + } + return PEAR::raiseError( + "Cannot read file '$file'.", + HTTP_DOWNLOAD_E_INVALID_FILE + ); + } + $this->setLastModified(filemtime($file)); + $this->file = $file; + $this->size = filesize($file); + return true; + } + + /** + * Set data for download + * + * Set $data to null if you want to unset this. + * + * @access public + * @return void + * @param $data raw data to send + */ + function setData($data = null) + { + $this->data = $data; + $this->size = strlen($data); + } + + /** + * Set resource for download + * + * The resource handle supplied will be closed after sending the download. + * Returns a PEAR_Error (HTTP_DOWNLOAD_E_INVALID_RESOURCE) if $handle + * is no valid resource. Set $handle to null if you want to unset this. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param int $handle resource handle + */ + function setResource($handle = null) + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + if (!isset($handle)) { + $this->handle = null; + $this->size = 0; + return true; + } + + if (is_resource($handle)) { + $this->handle = $handle; + $filestats = fstat($handle); + $this->size = isset($filestats['size']) ? $filestats['size'] + : -1; + return true; + } + + return PEAR::raiseError( + "Handle '$handle' is no valid resource.", + HTTP_DOWNLOAD_E_INVALID_RESOURCE + ); + } + + /** + * Whether to gzip the download + * + * Returns a PEAR_Error (HTTP_DOWNLOAD_E_NO_EXT_ZLIB) + * if ext/zlib is not available/loadable. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param bool $gzip whether to gzip the download + */ + function setGzip($gzip = false) + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + if ($gzip && !PEAR::loadExtension('zlib')){ + return PEAR::raiseError( + 'GZIP compression (ext/zlib) not available.', + HTTP_DOWNLOAD_E_NO_EXT_ZLIB + ); + } + $this->gzip = (bool) $gzip; + return true; + } + + /** + * Whether to allow caching + * + * If set to true (default) we'll send some headers that are commonly + * used for caching purposes like ETag, Cache-Control and Last-Modified. + * + * If caching is disabled, we'll send the download no matter if it + * would actually be cached at the client side. + * + * @access public + * @return void + * @param bool $cache whether to allow caching + */ + function setCache($cache = true) + { + $this->cache = (bool) $cache; + } + + /** + * Whether to allow proxies to cache + * + * If set to 'private' proxies shouldn't cache the response. + * This setting defaults to 'public' and affects only cached responses. + * + * @access public + * @return bool + * @param string $cache private or public + * @param int $maxage maximum age of the client cache entry + */ + function setCacheControl($cache = 'public', $maxage = 0) + { + switch ($cache = strToLower($cache)) + { + case 'private': + case 'public': + $this->headers['Cache-Control'] = + $cache .', must-revalidate, max-age='. abs($maxage); + return true; + break; + } + return false; + } + + /** + * Set ETag + * + * Sets a user-defined ETag for cache-validation. The ETag is usually + * generated by HTTP_Download through its payload information. + * + * @access public + * @return void + * @param string $etag Entity tag used for strong cache validation. + */ + function setETag($etag = null) + { + $this->etag = (string) $etag; + } + + /** + * Set Size of Buffer + * + * The amount of bytes specified as buffer size is the maximum amount + * of data read at once from resources or files. The default size is 2M + * (2097152 bytes). Be aware that if you enable gzip compression and + * you set a very low buffer size that the actual file size may grow + * due to added gzip headers for each sent chunk of the specified size. + * + * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_PARAM) if $size is not + * greater than 0 bytes. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param int $bytes Amount of bytes to use as buffer. + */ + function setBufferSize($bytes = 2097152) + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + if (0 >= $bytes) { + return PEAR::raiseError( + 'Buffer size must be greater than 0 bytes ('. $bytes .' given)', + HTTP_DOWNLOAD_E_INVALID_PARAM); + } + $this->bufferSize = abs($bytes); + return true; + } + + /** + * Set Throttle Delay + * + * Set the amount of seconds to sleep after each chunck that has been + * sent. One can implement some sort of throttle through adjusting the + * buffer size and the throttle delay. With the following settings + * HTTP_Download will sleep a second after each 25 K of data sent. + * + * <code> + * Array( + * 'throttledelay' => 1, + * 'buffersize' => 1024 * 25, + * ) + * </code> + * + * Just be aware that if gzipp'ing is enabled, decreasing the chunk size + * too much leads to proportionally increased network traffic due to added + * gzip header and bottom bytes around each chunk. + * + * @access public + * @return void + * @param float $seconds Amount of seconds to sleep after each + * chunk that has been sent. + */ + function setThrottleDelay($seconds = 0) + { + $this->throttleDelay = abs($seconds) * 1000; + } + + /** + * Set "Last-Modified" + * + * This is usually determined by filemtime() in HTTP_Download::setFile() + * If you set raw data for download with HTTP_Download::setData() and you + * want do send an appropiate "Last-Modified" header, you should call this + * method. + * + * @access public + * @return void + * @param int unix timestamp + */ + function setLastModified($last_modified) + { + $this->lastModified = $this->headers['Last-Modified'] = (int) $last_modified; + } + + /** + * Set Content-Disposition header + * + * @see HTTP_Download::HTTP_Download + * + * @access public + * @return void + * @param string $disposition whether to send the download + * inline or as attachment + * @param string $file_name the filename to display in + * the browser's download window + * + * <b>Example:</b> + * <code> + * $HTTP_Download->setContentDisposition( + * HTTP_DOWNLOAD_ATTACHMENT, + * 'download.tgz' + * ); + * </code> + */ + function setContentDisposition( $disposition = HTTP_DOWNLOAD_ATTACHMENT, + $file_name = null) + { + $cd = $disposition; + if (isset($file_name)) { + $cd .= '; filename="' . $file_name . '"'; + } elseif ($this->file) { + $cd .= '; filename="' . basename($this->file) . '"'; + } + $this->headers['Content-Disposition'] = $cd; + } + + /** + * Set content type of the download + * + * Default content type of the download will be 'application/x-octetstream'. + * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) if + * $content_type doesn't seem to be valid. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $content_type content type of file for download + */ + function setContentType($content_type = 'application/x-octetstream') + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + if (!preg_match('/^[a-z]+\w*\/[a-z]+[\w.;= -]*$/', $content_type)) { + return PEAR::raiseError( + "Invalid content type '$content_type' supplied.", + HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE + ); + } + $this->headers['Content-Type'] = $content_type; + return true; + } + + /** + * Guess content type of file + * + * First we try to use PEAR::MIME_Type, if installed, to detect the content + * type, else we check if ext/mime_magic is loaded and properly configured. + * + * Returns PEAR_Error if: + * o if PEAR::MIME_Type failed to detect a proper content type + * (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) + * o ext/magic.mime is not installed, or not properly configured + * (HTTP_DOWNLOAD_E_NO_EXT_MMAGIC) + * o mime_content_type() couldn't guess content type or returned + * a content type considered to be bogus by setContentType() + * (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + */ + function guessContentType() + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + if (class_exists('MIME_Type') || @include_once 'MIME/Type.php') { + if (PEAR::isError($mime_type = MIME_Type::autoDetect($this->file))) { + return PEAR::raiseError($mime_type->getMessage(), + HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE); + } + return $this->setContentType($mime_type); + } + if (!function_exists('mime_content_type')) { + return PEAR::raiseError( + 'This feature requires ext/mime_magic!', + HTTP_DOWNLOAD_E_NO_EXT_MMAGIC + ); + } + if (!is_file(ini_get('mime_magic.magicfile'))) { + return PEAR::raiseError( + 'ext/mime_magic is loaded but not properly configured!', + HTTP_DOWNLOAD_E_NO_EXT_MMAGIC + ); + } + if (!$content_type = @mime_content_type($this->file)) { + return PEAR::raiseError( + 'Couldn\'t guess content type with mime_content_type().', + HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE + ); + } + return $this->setContentType($content_type); + } + + /** + * Send + * + * Returns PEAR_Error if: + * o HTTP headers were already sent (HTTP_DOWNLOAD_E_HEADERS_SENT) + * o HTTP Range was invalid (HTTP_DOWNLOAD_E_INVALID_REQUEST) + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param bool $autoSetContentDisposition Whether to set the + * Content-Disposition header if it isn't already. + */ + function send($autoSetContentDisposition = true) + { + $error = $this->_getError(); + if ($error !== null) { + return $error; + } + if (headers_sent()) { + return PEAR::raiseError( + 'Headers already sent.', + HTTP_DOWNLOAD_E_HEADERS_SENT + ); + } + + if (!ini_get('safe_mode')) { + @set_time_limit(0); + } + + if ($autoSetContentDisposition && + !isset($this->headers['Content-Disposition'])) { + $this->setContentDisposition(); + } + + if ($this->cache) { + $this->headers['ETag'] = $this->generateETag(); + if ($this->isCached()) { + $this->HTTP->sendStatusCode(304); + $this->sendHeaders(); + return true; + } + } else { + unset($this->headers['Last-Modified']); + } + + if (ob_get_level()) { + while (@ob_end_clean()); + } + + if ($this->gzip) { + @ob_start('ob_gzhandler'); + } else { + ob_start(); + } + + $this->sentBytes = 0; + + // Known content length? + $end = ($this->size >= 0) ? max($this->size - 1, 0) : '*'; + + if ($end != '*' && $this->isRangeRequest()) { + $chunks = $this->getChunks(); + if (empty($chunks)) { + $this->HTTP->sendStatusCode(200); + $chunks = array(array(0, $end)); + + } elseif (PEAR::isError($chunks)) { + ob_end_clean(); + $this->HTTP->sendStatusCode(416); + return $chunks; + + } else { + $this->HTTP->sendStatusCode(206); + } + } else { + $this->HTTP->sendStatusCode(200); + $chunks = array(array(0, $end)); + if (!$this->gzip && count(ob_list_handlers()) < 2 && $end != '*') { + $this->headers['Content-Length'] = $this->size; + } + } + + $this->sendChunks($chunks); + + ob_end_flush(); + flush(); + return true; + } + + /** + * Static send + * + * @see HTTP_Download::HTTP_Download() + * @see HTTP_Download::send() + * + * @static + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $params associative array of parameters + * @param bool $guess whether HTTP_Download::guessContentType() + * should be called + */ + function staticSend($params, $guess = false) + { + $d = &new HTTP_Download(); + $e = $d->setParams($params); + if (PEAR::isError($e)) { + return $e; + } + if ($guess) { + $e = $d->guessContentType(); + if (PEAR::isError($e)) { + return $e; + } + } + return $d->send(); + } + + /** + * Send a bunch of files or directories as an archive + * + * Example: + * <code> + * require_once 'HTTP/Download.php'; + * HTTP_Download::sendArchive( + * 'myArchive.tgz', + * '/var/ftp/pub/mike', + * HTTP_DOWNLOAD_TGZ, + * '', + * '/var/ftp/pub' + * ); + * </code> + * + * @see Archive_Tar::createModify() + * @deprecated use HTTP_Download_Archive::send() + * @static + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $name name the sent archive should have + * @param mixed $files files/directories + * @param string $type archive type + * @param string $add_path path that should be prepended to the files + * @param string $strip_path path that should be stripped from the files + */ + function sendArchive( $name, + $files, + $type = HTTP_DOWNLOAD_TGZ, + $add_path = '', + $strip_path = '') + { + require_once 'HTTP/Download/Archive.php'; + return HTTP_Download_Archive::send($name, $files, $type, + $add_path, $strip_path); + } + // }}} + + // {{{ protected methods + /** + * Generate ETag + * + * @access protected + * @return string + */ + function generateETag() + { + if (!$this->etag) { + if ($this->data) { + $md5 = md5($this->data); + } else { + $mtime = time(); + $ino = 0; + $size = mt_rand(); + extract(is_resource($this->handle) ? fstat($this->handle) + : stat($this->file)); + $md5 = md5($mtime .'='. $ino .'='. $size); + } + $this->etag = '"' . $md5 . '-' . crc32($md5) . '"'; + } + return $this->etag; + } + + /** + * Send multiple chunks + * + * @access protected + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $chunks + */ + function sendChunks($chunks) + { + if (count($chunks) == 1) { + return $this->sendChunk(current($chunks)); + } + + $bound = uniqid('HTTP_DOWNLOAD-', true); + $cType = $this->headers['Content-Type']; + $this->headers['Content-Type'] = + 'multipart/byteranges; boundary=' . $bound; + $this->sendHeaders(); + foreach ($chunks as $chunk){ + $this->sendChunk($chunk, $cType, $bound); + } + #echo "\r\n--$bound--\r\n"; + return true; + } + + /** + * Send chunk of data + * + * @access protected + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $chunk start and end offset of the chunk to send + * @param string $cType actual content type + * @param string $bound boundary for multipart/byteranges + */ + function sendChunk($chunk, $cType = null, $bound = null) + { + list($offset, $lastbyte) = $chunk; + $length = ($lastbyte - $offset) + 1; + + $range = $offset . '-' . $lastbyte . '/' + . (($this->size >= 0) ? $this->size : '*'); + + if (isset($cType, $bound)) { + echo "\r\n--$bound\r\n", + "Content-Type: $cType\r\n", + "Content-Range: bytes $range\r\n\r\n"; + } else { + if ($lastbyte != '*' && $this->isRangeRequest()) { + $this->headers['Content-Length'] = $length; + $this->headers['Content-Range'] = 'bytes '. $range; + } + $this->sendHeaders(); + } + + if ($this->data) { + while (($length -= $this->bufferSize) > 0) { + $this->flush(substr($this->data, $offset, $this->bufferSize)); + $this->throttleDelay and $this->sleep(); + $offset += $this->bufferSize; + } + if ($length) { + $this->flush(substr($this->data, $offset, $this->bufferSize + $length)); + } + } else { + if (!is_resource($this->handle)) { + $this->handle = fopen($this->file, 'rb'); + } + fseek($this->handle, $offset); + if ($lastbyte == '*') { + while (!feof($this->handle)) { + $this->flush(fread($this->handle, $this->bufferSize)); + $this->throttleDelay and $this->sleep(); + } + } else { + while (($length -= $this->bufferSize) > 0) { + $this->flush(fread($this->handle, $this->bufferSize)); + $this->throttleDelay and $this->sleep(); + } + if ($length) { + $this->flush(fread($this->handle, $this->bufferSize + $length)); + } + } + } + return true; + } + + /** + * Get chunks to send + * + * @access protected + * @return array Chunk list or PEAR_Error on invalid range request + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 + */ + function getChunks() + { + $end = ($this->size >= 0) ? max($this->size - 1, 0) : '*'; + + // Trying to handle ranges on content with unknown length is too + // big of a mess (impossible to determine if a range is valid) + if ($end == '*') { + return array(); + } + + $ranges = $this->getRanges(); + if (empty($ranges)) { + return array(); + } + + $parts = array(); + $satisfiable = false; + foreach (explode(',', $ranges) as $chunk){ + list($o, $e) = explode('-', trim($chunk)); + + // If the last-byte-pos value is present, it MUST be greater than + // or equal to the first-byte-pos in that byte-range-spec, or the + // byte- range-spec is syntactically invalid. The recipient of a + // byte-range- set that includes one or more syntactically invalid + // byte-range-spec values MUST ignore the header field that + // includes that byte-range- set. + if ($e !== '' && $o !== '' && $e < $o) { + return array(); + } + + // If the last-byte-pos value is absent, or if the value is + // greater than or equal to the current length of the entity-body, + // last-byte-pos is taken to be equal to one less than the current + // length of the entity- body in bytes. + if ($e === '' || $e > $end) { + $e = $end; + } + + // A suffix-byte-range-spec is used to specify the suffix of the + // entity-body, of a length given by the suffix-length value. (That + // is, this form specifies the last N bytes of an entity-body.) If + // the entity is shorter than the specified suffix-length, the + // entire entity-body is used. + if ($o === '') { + // If a syntactically valid byte-range-set includes at least + // one suffix-byte-range-spec with a non-zero suffix-length, + // then the byte-range-set is satisfiable. + $satisfiable |= ($e != 0); + + $o = max($this->size - $e, 0); + $e = $end; + + } elseif ($o <= $end) { + // If a syntactically valid byte-range-set includes at least + // one byte- range-spec whose first-byte-pos is less than the + // current length of the entity-body, then the byte-range-set + // is satisfiable. + $satisfiable = true; + } else { + continue; + } + + $parts[] = array($o, $e); + } + + // If the byte-range-set is unsatisfiable, the server SHOULD return a + // response with a status of 416 (Requested range not satisfiable). + if (!$satisfiable) { + $error = PEAR::raiseError('Error processing range request', + HTTP_DOWNLOAD_E_INVALID_REQUEST); + return $error; + } + //$this->sortChunks($parts); + return $this->mergeChunks($parts); + } + + /** + * Sorts the ranges to be in ascending order + * + * @param array &$chunks ranges to sort + * + * @return void + * @access protected + * @static + * @author Philippe Jausions <jausions@php.net> + */ + function sortChunks(&$chunks) + { + $sortFunc = create_function('$a,$b', + 'if ($a[0] == $b[0]) { + if ($a[1] == $b[1]) { + return 0; + } + return (($a[1] != "*" && $a[1] < $b[1]) + || $b[1] == "*") ? -1 : 1; + } + + return ($a[0] < $b[0]) ? -1 : 1;'); + + usort($chunks, $sortFunc); + } + + /** + * Merges consecutive chunks to avoid overlaps + * + * @param array $chunks Ranges to merge + * + * @return array merged ranges + * @access protected + * @static + * @author Philippe Jausions <jausions@php.net> + */ + function mergeChunks($chunks) + { + do { + $count = count($chunks); + $merged = array(current($chunks)); + $j = 0; + for ($i = 1; $i < count($chunks); ++$i) { + list($o, $e) = $chunks[$i]; + if ($merged[$j][1] == '*') { + if ($merged[$j][0] <= $o) { + continue; + } elseif ($e == '*' || $merged[$j][0] <= $e) { + $merged[$j][0] = min($merged[$j][0], $o); + } else { + $merged[++$j] = $chunks[$i]; + } + } elseif ($merged[$j][0] <= $o && $o <= $merged[$j][1]) { + $merged[$j][1] = ($e == '*') ? '*' : max($e, $merged[$j][1]); + } elseif ($merged[$j][0] <= $e && $e <= $merged[$j][1]) { + $merged[$j][0] = min($o, $merged[$j][0]); + } else { + $merged[++$j] = $chunks[$i]; + } + } + if ($count == count($merged)) { + break; + } + $chunks = $merged; + } while (true); + return $merged; + } + + /** + * Check if range is requested + * + * @access protected + * @return bool + */ + function isRangeRequest() + { + if (!isset($_SERVER['HTTP_RANGE']) || !count($this->getRanges())) { + return false; + } + return $this->isValidRange(); + } + + /** + * Get range request + * + * @access protected + * @return array + */ + function getRanges() + { + return preg_match('/^bytes=((\d+-|\d+-\d+|-\d+)(, ?(\d+-|\d+-\d+|-\d+))*)$/', + @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : array(); + } + + /** + * Check if entity is cached + * + * @access protected + * @return bool + */ + function isCached() + { + return ( + (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && + $this->lastModified == strtotime(current($a = explode( + ';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) || + (isset($_SERVER['HTTP_IF_NONE_MATCH']) && + $this->compareAsterisk('HTTP_IF_NONE_MATCH', $this->etag)) + ); + } + + /** + * Check if entity hasn't changed + * + * @access protected + * @return bool + */ + function isValidRange() + { + if (isset($_SERVER['HTTP_IF_MATCH']) && + !$this->compareAsterisk('HTTP_IF_MATCH', $this->etag)) { + return false; + } + if (isset($_SERVER['HTTP_IF_RANGE']) && + $_SERVER['HTTP_IF_RANGE'] !== $this->etag && + strtotime($_SERVER['HTTP_IF_RANGE']) !== $this->lastModified) { + return false; + } + if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) { + $lm = current($a = explode(';', $_SERVER['HTTP_IF_UNMODIFIED_SINCE'])); + if (strtotime($lm) !== $this->lastModified) { + return false; + } + } + if (isset($_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])) { + $lm = current($a = explode(';', $_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])); + if (strtotime($lm) !== $this->lastModified) { + return false; + } + } + return true; + } + + /** + * Compare against an asterisk or check for equality + * + * @access protected + * @return bool + * @param string key for the $_SERVER array + * @param string string to compare + */ + function compareAsterisk($svar, $compare) + { + foreach (array_map('trim', explode(',', $_SERVER[$svar])) as $request) { + if ($request === '*' || $request === $compare) { + return true; + } + } + return false; + } + + /** + * Send HTTP headers + * + * @access protected + * @return void + */ + function sendHeaders() + { + foreach ($this->headers as $header => $value) { + $this->HTTP->setHeader($header, $value); + } + $this->HTTP->sendHeaders(); + /* NSAPI won't output anything if we did this */ + if (strncasecmp(PHP_SAPI, 'nsapi', 5)) { + if (ob_get_level()) { + ob_flush(); + } + flush(); + } + } + + /** + * Flush + * + * @access protected + * @return void + * @param string $data + */ + function flush($data = '') + { + if ($dlen = strlen($data)) { + $this->sentBytes += $dlen; + echo $data; + } + ob_flush(); + flush(); + } + + /** + * Sleep + * + * @access protected + * @return void + */ + function sleep() + { + if (OS_WINDOWS) { + com_message_pump($this->throttleDelay); + } else { + usleep($this->throttleDelay * 1000); + } + } + + /** + * Returns and clears startup error + * + * @return NULL|PEAR_Error startup error if one exists + * @access protected + */ + function _getError() + { + $error = null; + if (PEAR::isError($this->_error)) { + $error = $this->_error; + $this->_error = null; + } + return $error; + } + // }}} +} +?> diff --git a/includes/pear/HTTP/Download/Archive.php b/includes/pear/HTTP/Download/Archive.php new file mode 100644 index 0000000..4eadbd7 --- /dev/null +++ b/includes/pear/HTTP/Download/Archive.php @@ -0,0 +1,122 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * HTTP::Download::Archive + * + * PHP versions 4 and 5 + * + * @category HTTP + * @package HTTP_Download + * @author Michael Wallner <mike@php.net> + * @copyright 2003-2005 Michael Wallner + * @license BSD, revisewd + * @version CVS: $Id: Archive.php 304423 2010-10-15 13:36:46Z clockwerx $ + * @link http://pear.php.net/package/HTTP_Download + */ + +/** + * Requires HTTP_Download + */ +require_once 'HTTP/Download.php'; + +/** + * Requires System + */ +require_once 'System.php'; + +/** + * HTTP_Download_Archive + * + * Helper class for sending Archives. + * + * @access public + * @version $Revision: 304423 $ + */ +class HTTP_Download_Archive +{ + /** + * Send a bunch of files or directories as an archive + * + * Example: + * <code> + * require_once 'HTTP/Download/Archive.php'; + * HTTP_Download_Archive::send( + * 'myArchive.tgz', + * '/var/ftp/pub/mike', + * HTTP_DOWNLOAD_BZ2, + * '', + * '/var/ftp/pub' + * ); + * </code> + * + * @see Archive_Tar::createModify() + * @static + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $name name the sent archive should have + * @param mixed $files files/directories + * @param string $type archive type + * @param string $add_path path that should be prepended to the files + * @param string $strip_path path that should be stripped from the files + */ + function send($name, $files, $type = HTTP_DOWNLOAD_TGZ, $add_path = '', $strip_path = '') + { + $tmp = System::mktemp(); + + switch ($type = strToUpper($type)) + { + case HTTP_DOWNLOAD_TAR: + include_once 'Archive/Tar.php'; + $arc = &new Archive_Tar($tmp); + $content_type = 'x-tar'; + break; + + case HTTP_DOWNLOAD_TGZ: + include_once 'Archive/Tar.php'; + $arc = &new Archive_Tar($tmp, 'gz'); + $content_type = 'x-gzip'; + break; + + case HTTP_DOWNLOAD_BZ2: + include_once 'Archive/Tar.php'; + $arc = &new Archive_Tar($tmp, 'bz2'); + $content_type = 'x-bzip2'; + break; + + case HTTP_DOWNLOAD_ZIP: + include_once 'Archive/Zip.php'; + $arc = &new Archive_Zip($tmp); + $content_type = 'x-zip'; + break; + + default: + return PEAR::raiseError( + 'Archive type not supported: ' . $type, + HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE + ); + } + + if ($type == HTTP_DOWNLOAD_ZIP) { + $options = array( 'add_path' => $add_path, + 'remove_path' => $strip_path); + if (!$arc->create($files, $options)) { + return PEAR::raiseError('Archive creation failed.'); + } + } else { + if (!$e = $arc->createModify($files, $add_path, $strip_path)) { + return PEAR::raiseError('Archive creation failed.'); + } + if (PEAR::isError($e)) { + return $e; + } + } + unset($arc); + + $dl = &new HTTP_Download(array('file' => $tmp)); + $dl->setContentType('application/' . $content_type); + $dl->setContentDisposition(HTTP_DOWNLOAD_ATTACHMENT, $name); + return $dl->send(); + } +} +?> diff --git a/includes/pear/HTTP/Download/PgLOB.php b/includes/pear/HTTP/Download/PgLOB.php new file mode 100644 index 0000000..d1c989e --- /dev/null +++ b/includes/pear/HTTP/Download/PgLOB.php @@ -0,0 +1,177 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * HTTP::Download::PgLOB + * + * PHP versions 4 and 5 + * + * @category HTTP + * @package HTTP_Download + * @author Michael Wallner <mike@php.net> + * @copyright 2003-2005 Michael Wallner + * @license BSD, revised + * @version CVS: $Id: PgLOB.php 304423 2010-10-15 13:36:46Z clockwerx $ + * @link http://pear.php.net/package/HTTP_Download + */ + +$GLOBALS['_HTTP_Download_PgLOB_Connection'] = null; +stream_register_wrapper('pglob', 'HTTP_Download_PgLOB'); + +/** + * PgSQL large object stream interface for HTTP_Download + * + * Usage: + * <code> + * require_once 'HTTP/Download.php'; + * require_once 'HTTP/Download/PgLOB.php'; + * $db = &DB::connect('pgsql://user:pass@host/db'); + * // or $db = pg_connect(...); + * $lo = HTTP_Download_PgLOB::open($db, 12345); + * $dl = &new HTTP_Download; + * $dl->setResource($lo); + * $dl->send() + * </code> + * + * @access public + * @version $Revision: 304423 $ + */ +class HTTP_Download_PgLOB +{ + /** + * Set Connection + * + * @static + * @access public + * @return bool + * @param mixed $conn + */ + function setConnection($conn) + { + if (is_a($conn, 'DB_Common')) { + $conn = $conn->dbh; + } elseif ( is_a($conn, 'MDB_Common') || + is_a($conn, 'MDB2_Driver_Common')) { + $conn = $conn->connection; + } + if ($isResource = is_resource($conn)) { + $GLOBALS['_HTTP_Download_PgLOB_Connection'] = $conn; + } + return $isResource; + } + + /** + * Get Connection + * + * @static + * @access public + * @return resource + */ + function getConnection() + { + if (is_resource($GLOBALS['_HTTP_Download_PgLOB_Connection'])) { + return $GLOBALS['_HTTP_Download_PgLOB_Connection']; + } + return null; + } + + /** + * Open + * + * @static + * @access public + * @return resource + * @param mixed $conn + * @param int $loid + * @param string $mode + */ + function open($conn, $loid, $mode = 'rb') + { + HTTP_Download_PgLOB::setConnection($conn); + return fopen('pglob:///'. $loid, $mode); + } + + /**#@+ + * Stream Interface Implementation + * @internal + */ + var $ID = 0; + var $size = 0; + var $conn = null; + var $handle = null; + + function stream_open($path, $mode) + { + if (!$this->conn = HTTP_Download_PgLOB::getConnection()) { + return false; + } + if (!preg_match('/(\d+)/', $path, $matches)) { + return false; + } + $this->ID = $matches[1]; + + if (!pg_query($this->conn, 'BEGIN')) { + return false; + } + + $this->handle = pg_lo_open($this->conn, $this->ID, $mode); + if (!is_resource($this->handle)) { + return false; + } + + // fetch size of lob + pg_lo_seek($this->handle, 0, PGSQL_SEEK_END); + $this->size = (int) pg_lo_tell($this->handle); + pg_lo_seek($this->handle, 0, PGSQL_SEEK_SET); + + return true; + } + + function stream_read($length) + { + return pg_lo_read($this->handle, $length); + } + + function stream_seek($offset, $whence = SEEK_SET) + { + return pg_lo_seek($this->handle, $offset, $whence); + } + + function stream_tell() + { + return pg_lo_tell($this->handle); + } + + function stream_eof() + { + return pg_lo_tell($this->handle) >= $this->size; + } + + function stream_flush() + { + return true; + } + + function stream_stat() + { + return array('size' => $this->size, 'ino' => $this->ID); + } + + function stream_write($data) + { + return pg_lo_write($this->handle, $data); + } + + function stream_close() + { + if (pg_lo_close($this->handle)) { + return pg_query($this->conn, 'COMMIT'); + } else { + pg_query($this->conn ,'ROLLBACK'); + return false; + } + } + /**#@-*/ +} + +?> diff --git a/includes/pear/HTTP/Header.php b/includes/pear/HTTP/Header.php new file mode 100644 index 0000000..14026ad --- /dev/null +++ b/includes/pear/HTTP/Header.php @@ -0,0 +1,537 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * HTTP::Header + * + * PHP versions 4 and 5 + * + * @category HTTP + * @package HTTP_Header + * @author Wolfram Kriesing <wk@visionp.de> + * @author Davey Shafik <davey@php.net> + * @author Michael Wallner <mike@php.net> + * @copyright 2003-2005 The Authors + * @license BSD, revised + * @version CVS: $Id: Header.php 304418 2010-10-15 13:18:02Z clockwerx $ + * @link http://pear.php.net/package/HTTP_Header + */ + +/** + * Requires HTTP + */ +require_once 'HTTP.php'; + +/**#@+ + * Information Codes + */ +define('HTTP_HEADER_STATUS_100', '100 Continue'); +define('HTTP_HEADER_STATUS_101', '101 Switching Protocols'); +define('HTTP_HEADER_STATUS_102', '102 Processing'); +define('HTTP_HEADER_STATUS_INFORMATIONAL',1); +/**#@-*/ + +/**#+ + * Success Codes + */ +define('HTTP_HEADER_STATUS_200', '200 OK'); +define('HTTP_HEADER_STATUS_201', '201 Created'); +define('HTTP_HEADER_STATUS_202', '202 Accepted'); +define('HTTP_HEADER_STATUS_203', '203 Non-Authoritative Information'); +define('HTTP_HEADER_STATUS_204', '204 No Content'); +define('HTTP_HEADER_STATUS_205', '205 Reset Content'); +define('HTTP_HEADER_STATUS_206', '206 Partial Content'); +define('HTTP_HEADER_STATUS_207', '207 Multi-Status'); +define('HTTP_HEADER_STATUS_SUCCESSFUL',2); +/**#@-*/ + +/**#@+ + * Redirection Codes + */ +define('HTTP_HEADER_STATUS_300', '300 Multiple Choices'); +define('HTTP_HEADER_STATUS_301', '301 Moved Permanently'); +define('HTTP_HEADER_STATUS_302', '302 Found'); +define('HTTP_HEADER_STATUS_303', '303 See Other'); +define('HTTP_HEADER_STATUS_304', '304 Not Modified'); +define('HTTP_HEADER_STATUS_305', '305 Use Proxy'); +define('HTTP_HEADER_STATUS_306', '306 (Unused)'); +define('HTTP_HEADER_STATUS_307', '307 Temporary Redirect'); +define('HTTP_HEADER_STATUS_REDIRECT',3); +/**#@-*/ + +/**#@+ + * Error Codes + */ +define('HTTP_HEADER_STATUS_400', '400 Bad Request'); +define('HTTP_HEADER_STATUS_401', '401 Unauthorized'); +define('HTTP_HEADER_STATUS_402', '402 Payment Granted'); +define('HTTP_HEADER_STATUS_403', '403 Forbidden'); +define('HTTP_HEADER_STATUS_404', '404 File Not Found'); +define('HTTP_HEADER_STATUS_405', '405 Method Not Allowed'); +define('HTTP_HEADER_STATUS_406', '406 Not Acceptable'); +define('HTTP_HEADER_STATUS_407', '407 Proxy Authentication Required'); +define('HTTP_HEADER_STATUS_408', '408 Request Time-out'); +define('HTTP_HEADER_STATUS_409', '409 Conflict'); +define('HTTP_HEADER_STATUS_410', '410 Gone'); +define('HTTP_HEADER_STATUS_411', '411 Length Required'); +define('HTTP_HEADER_STATUS_412', '412 Precondition Failed'); +define('HTTP_HEADER_STATUS_413', '413 Request Entity Too Large'); +define('HTTP_HEADER_STATUS_414', '414 Request-URI Too Large'); +define('HTTP_HEADER_STATUS_415', '415 Unsupported Media Type'); +define('HTTP_HEADER_STATUS_416', '416 Requested range not satisfiable'); +define('HTTP_HEADER_STATUS_417', '417 Expectation Failed'); +define('HTTP_HEADER_STATUS_422', '422 Unprocessable Entity'); +define('HTTP_HEADER_STATUS_423', '423 Locked'); +define('HTTP_HEADER_STATUS_424', '424 Failed Dependency'); +define('HTTP_HEADER_STATUS_CLIENT_ERROR',4); +/**#@-*/ + +/**#@+ + * Server Errors + */ +define('HTTP_HEADER_STATUS_500', '500 Internal Server Error'); +define('HTTP_HEADER_STATUS_501', '501 Not Implemented'); +define('HTTP_HEADER_STATUS_502', '502 Bad Gateway'); +define('HTTP_HEADER_STATUS_503', '503 Service Unavailable'); +define('HTTP_HEADER_STATUS_504', '504 Gateway Time-out'); +define('HTTP_HEADER_STATUS_505', '505 HTTP Version not supported'); +define('HTTP_HEADER_STATUS_507', '507 Insufficient Storage'); +define('HTTP_HEADER_STATUS_SERVER_ERROR',5); +/**#@-*/ + +/** + * HTTP_Header + * + * @package HTTP_Header + * @category HTTP + * @access public + * @version $Revision: 304418 $ + */ +class HTTP_Header extends HTTP +{ + /** + * Default Headers + * + * The values that are set as default, are the same as PHP sends by default. + * + * @var array + * @access private + */ + var $_headers = array( + 'content-type' => 'text/html', + 'pragma' => 'no-cache', + 'cache-control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' + ); + + /** + * HTTP version + * + * @var string + * @access private + */ + var $_httpVersion = '1.0'; + + /** + * @var bool + * @access public + */ + var $prettify = false; + + /** + * Constructor + * + * Sets HTTP version. + * + * @access public + * @return object HTTP_Header + */ + function HTTP_Header() + { + if (isset($_SERVER['SERVER_PROTOCOL'])) { + $this->setHttpVersion(substr($_SERVER['SERVER_PROTOCOL'], -3)); + } + } + + /** + * Set HTTP version + * + * @access public + * @return bool Returns true on success or false if version doesn't + * match 1.0 or 1.1 (note: 1 will result in 1.0) + * @param mixed $version HTTP version, either 1.0 or 1.1 + */ + function setHttpVersion($version) + { + $version = round((float) $version, 1); + if ($version < 1.0 || $version > 1.1) { + return false; + } + $this->_httpVersion = sprintf('%0.1f', $version); + return true; + } + + /** + * Get HTTP version + * + * @access public + * @return string + */ + function getHttpVersion() + { + return $this->_httpVersion; + } + + /** + * Set Header + * + * The default value for the Last-Modified header will be current + * date and atime if $value is omitted. + * + * @access public + * @return bool Returns true on success or false if $key was empty or + * $value was not of an scalar type. + * @param string $key The name of the header. + * @param string $value The value of the header. (NULL to unset header) + */ + function setHeader($key, $value = null) + { + if (empty($key) || (isset($value) && !is_scalar($value))) { + return false; + } + + $key = strToLower($key); + if ($key == 'last-modified') { + if (!isset($value)) { + $value = HTTP::Date(time()); + } elseif (is_numeric($value)) { + $value = HTTP::Date($value); + } + } + + if (isset($value)) { + $this->_headers[$key] = $value; + } else { + unset($this->_headers[$key]); + } + + return true; + } + + /** + * Get Header + * + * If $key is omitted, all stored headers will be returned. + * + * @access public + * @return mixed Returns string value of the requested header, + * array values of all headers or false if header $key + * is not set. + * @param string $key The name of the header to fetch. + */ + function getHeader($key = null) + { + if (!isset($key)) { + return $this->_headers; + } + + $key = strToLower($key); + + if (!isset($this->_headers[$key])) { + return false; + } + + return $this->_headers[$key]; + } + + /** + * Send Headers + * + * Send out the header that you set via setHeader(). + * + * @access public + * @return bool Returns true on success or false if headers are already + * sent. + * @param array $keys Headers to (not) send, see $include. + * @param array $include If true only $keys matching headers will be + * sent, if false only header not matching $keys will be + * sent. + */ + function sendHeaders($keys = array(), $include = true) + { + if (headers_sent()) { + return false; + } + + if (count($keys)) { + array_change_key_case($keys, CASE_LOWER); + foreach ($this->_headers as $key => $value) { + if ($include ? in_array($key, $keys) : !in_array($key, $keys)) { + header(($this->prettify ? uctitle($key) : $key) .': '. $value); + } + } + } else { + foreach ($this->_headers as $header => $value) { + header(($this->prettify ? uctitle($header) : $header) .': '. $value); + } + } + return true; + } + + /** + * Send Satus Code + * + * Send out the given HTTP-Status code. Use this for example when you + * want to tell the client this page is cached, then you would call + * sendStatusCode(304). + * + * @see HTTP_Header_Cache::exitIfCached() + * + * @access public + * @return bool Returns true on success or false if headers are already + * sent. + * @param int $code The status code to send, i.e. 404, 304, 200, etc. + */ + function sendStatusCode($code) + { + if (headers_sent()) { + return false; + } + + if ($code == (int) $code && defined('HTTP_HEADER_STATUS_'. $code)) { + $code = constant('HTTP_HEADER_STATUS_'. $code); + } + + if (strncasecmp(PHP_SAPI, 'cgi', 3)) { + header('HTTP/'. $this->_httpVersion .' '. $code); + } else { + header('Status: '. $code); + } + return true; + } + + /** + * Date to Timestamp + * + * Converts dates like + * Mon, 31 Mar 2003 15:26:34 GMT + * Tue, 15 Nov 1994 12:45:26 GMT + * into a timestamp, strtotime() didn't do it in older versions. + * + * @deprecated Use PHPs strtotime() instead. + * @access public + * @return mixed Returns int unix timestamp or false if the date doesn't + * seem to be a valid GMT date. + * @param string $date The GMT date. + */ + function dateToTimestamp($date) + { + static $months = array( + null => 0, 'Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, + 'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, + 'Oct' => 10, 'Nov' => 11, 'Dec' => 12 + ); + + if (-1 < $timestamp = strToTime($date)) { + return $timestamp; + } + + if (!preg_match('~[^,]*,\s(\d+)\s(\w+)\s(\d+)\s(\d+):(\d+):(\d+).*~', + $date, $m)) { + return false; + } + + // [0] => Mon, 31 Mar 2003 15:42:55 GMT + // [1] => 31 [2] => Mar [3] => 2003 [4] => 15 [5] => 42 [6] => 55 + return mktime($m[4], $m[5], $m[6], $months[$m[2]], $m[1], $m[3]); + } + + /** + * Redirect + * + * This function redirects the client. This is done by issuing a Location + * header and exiting. Additionally to HTTP::redirect() you can also add + * parameters to the url. + * + * If you dont need parameters to be added, simply use HTTP::redirect() + * otherwise use HTTP_Header::redirect(). + * + * @see HTTP::redirect() + * @author Wolfram Kriesing <wk@visionp.de> + * @access public + * @return void + * @param string $url The URL to redirect to, if none is given it + * redirects to the current page. + * @param array $param Array of query string parameters to add; usually + * a set of key => value pairs; if an array entry consists + * only of an value it is used as key and the respective + * value is fetched from $GLOBALS[$value] + * @param bool $session Whether the session name/id should be added + */ + function redirect($url = null, $param = array(), $session = false) + { + if (!isset($url)) { + $url = $_SERVER['PHP_SELF']; + } + + $qs = array(); + + if ($session) { + $qs[] = session_name() .'='. session_id(); + } + + if (is_array($param) && count($param)) { + if (count($param)) { + foreach ($param as $key => $val) { + if (is_string($key)) { + $qs[] = urlencode($key) .'='. urlencode($val); + } else { + $qs[] = urlencode($val) .'='. urlencode(@$GLOBALS[$val]); + } + } + } + } + + if ($qstr = implode('&', $qs)) { + $purl = parse_url($url); + $url .= (isset($purl['query']) ? '&' : '?') . $qstr; + } + + parent::redirect($url); + } + + /**#@+ + * @author Davey Shafik <davey@php.net> + * @param int $http_code HTTP Code to check + * @access public + */ + + /** + * Return HTTP Status Code Type + * + * @return int|false + */ + function getStatusType($http_code) + { + if(is_int($http_code) && defined('HTTP_HEADER_STATUS_' .$http_code) || defined($http_code)) { + $type = substr($http_code,0,1); + switch ($type) { + case HTTP_HEADER_STATUS_INFORMATIONAL: + case HTTP_HEADER_STATUS_SUCCESSFUL: + case HTTP_HEADER_STATUS_REDIRECT: + case HTTP_HEADER_STATUS_CLIENT_ERROR: + case HTTP_HEADER_STATUS_SERVER_ERROR: + return $type; + break; + default: + return false; + break; + } + } else { + return false; + } + } + + /** + * Return Status Code Message + * + * @return string|false + */ + function getStatusText($http_code) + { + if ($this->getStatusType($http_code)) { + if (is_int($http_code) && defined('HTTP_HEADER_STATUS_' .$http_code)) { + return substr(constant('HTTP_HEADER_STATUS_' .$http_code),4); + } else { + return substr($http_code,4); + } + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Information (1xx) + * + * @return boolean + */ + function isInformational($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_INFORMATIONAL; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Successful (2xx) + * + * @return boolean + */ + function isSuccessful($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_SUCCESSFUL; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is a Redirect (3xx) + * + * @return boolean + */ + function isRedirect($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_REDIRECT; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is a Client Error (4xx) + * + * @return boolean + */ + function isClientError($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_CLIENT_ERROR; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Server Error (5xx) + * + * @return boolean + */ + function isServerError($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_SERVER_ERROR; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Server OR Client Error (4xx or 5xx) + * + * @return boolean + */ + function isError($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return (($status_type == HTTP_HEADER_STATUS_CLIENT_ERROR) || ($status_type == HTTP_HEADER_STATUS_SERVER_ERROR)) ? true : false; + } else { + return false; + } + } + /**#@-*/ +} +?> diff --git a/includes/pear/HTTP/Header/Cache.php b/includes/pear/HTTP/Header/Cache.php new file mode 100644 index 0000000..f6ae867 --- /dev/null +++ b/includes/pear/HTTP/Header/Cache.php @@ -0,0 +1,238 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * HTTP::Header::Cache + * + * PHP versions 4 and 5 + * + * @category HTTP + * @package HTTP_Header + * @author Wolfram Kriesing <wk@visionp.de> + * @author Michael Wallner <mike@php.net> + * @copyright 2003-2005 The Authors + * @license BSD, revised + * @version CVS: $Id: Cache.php 304418 2010-10-15 13:18:02Z clockwerx $ + * @link http://pear.php.net/package/HTTP_Header + */ + +/** + * Requires HTTP_Header + */ +require_once 'HTTP/Header.php'; + +/** + * HTTP_Header_Cache + * + * This package provides methods to easier handle caching of HTTP pages. That + * means that the pages can be cached at the client (user agent or browser) and + * your application only needs to send "hey client you already have the pages". + * + * Which is done by sending the HTTP-Status "304 Not Modified", so that your + * application load and the network traffic can be reduced, since you only need + * to send the complete page once. This is really an advantage e.g. for + * generated style sheets, or simply pages that do only change rarely. + * + * Usage: + * <code> + * require_once 'HTTP/Header/Cache.php'; + * $httpCache = new HTTP_Header_Cache(4, 'weeks'); + * $httpCache->sendHeaders(); + * // your code goes here + * </code> + * + * @package HTTP_Header + * @category HTTP + * @access public + * @version $Revision: 304418 $ + */ +class HTTP_Header_Cache extends HTTP_Header +{ + /** + * Constructor + * + * Set the amount of time to cache. + * + * @access public + * @return object HTTP_Header_Cache + * @param int $expires + * @param string $unit + */ + function HTTP_Header_Cache($expires = 0, $unit = 'seconds') + { + parent::HTTP_Header(); + $this->setHeader('Pragma', 'cache'); + $this->setHeader('Last-Modified', $this->getCacheStart()); + $this->setHeader('Cache-Control', 'private, must-revalidate, max-age=0'); + + if ($expires) { + if (!$this->isOlderThan($expires, $unit)) { + $this->exitCached(); + } + $this->setHeader('Last-Modified', time()); + } + } + + /** + * Get Cache Start + * + * Returns the unix timestamp of the If-Modified-Since HTTP header or the + * current time if the header was not sent by the client. + * + * @access public + * @return int unix timestamp + */ + function getCacheStart() + { + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !$this->isPost()) { + return strtotime(current($array = explode(';', + $_SERVER['HTTP_IF_MODIFIED_SINCE']))); + } + return time(); + } + + /** + * Is Older Than + * + * You can call it like this: + * <code> + * $httpCache->isOlderThan(1, 'day'); + * $httpCache->isOlderThan(47, 'days'); + * + * $httpCache->isOlderThan(1, 'week'); + * $httpCache->isOlderThan(3, 'weeks'); + * + * $httpCache->isOlderThan(1, 'hour'); + * $httpCache->isOlderThan(5, 'hours'); + * + * $httpCache->isOlderThan(1, 'minute'); + * $httpCache->isOlderThan(15, 'minutes'); + * + * $httpCache->isOlderThan(1, 'second'); + * $httpCache->isOlderThan(15); + * </code> + * + * If you specify something greater than "weeks" as time untit, it just + * works approximatly, because a month is taken to consist of 4.3 weeks. + * + * @access public + * @return bool Returns true if requested page is older than specified. + * @param int $time The amount of time. + * @param string $unit The unit of the time amount - (year[s], month[s], + * week[s], day[s], hour[s], minute[s], second[s]). + */ + function isOlderThan($time = 0, $unit = 'seconds') + { + if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || $this->isPost()) { + return true; + } + if (!$time) { + return false; + } + + switch (strtolower($unit)) + { + case 'year': + case 'years': + $time *= 12; + case 'month': + case 'months': + $time *= 4.3; + case 'week': + case 'weeks': + $time *= 7; + case 'day': + case 'days': + $time *= 24; + case 'hour': + case 'hours': + $time *= 60; + case 'minute': + case 'minutes': + $time *= 60; + } + + return (time() - $this->getCacheStart()) > $time; + } + + /** + * Is Cached + * + * Check whether we can consider to be cached on the client side. + * + * @access public + * @return bool Whether the page/resource is considered to be cached. + * @param int $lastModified Unix timestamp of last modification. + */ + function isCached($lastModified = 0) + { + if ($this->isPost()) { + return false; + } + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !$lastModified) { + return true; + } + if (!$seconds = time() - $lastModified) { + return false; + } + return !$this->isOlderThan($seconds); + } + + /** + * Is Post + * + * Check if request method is "POST". + * + * @access public + * @return bool + */ + function isPost() + { + return isset($_SERVER['REQUEST_METHOD']) and + 'POST' == $_SERVER['REQUEST_METHOD']; + } + + /** + * Exit If Cached + * + * Exit with "HTTP 304 Not Modified" if we consider to be cached. + * + * @access public + * @return void + * @param int $lastModified Unix timestamp of last modification. + */ + function exitIfCached($lastModified = 0) + { + if ($this->isCached($lastModified)) { + $this->exitCached(); + } + } + + /** + * Exit Cached + * + * Exit with "HTTP 304 Not Modified". + * + * @access public + * @return void + */ + function exitCached() + { + $this->sendHeaders(); + $this->sendStatusCode(304); + exit; + } + + /** + * Set Last Modified + * + * @access public + * @return void + * @param int $lastModified The unix timestamp of last modification. + */ + function setLastModified($lastModified = null) + { + $this->setHeader('Last-Modified', $lastModified); + } +} +?> diff --git a/includes/pear/Image/GraphViz.php b/includes/pear/Image/GraphViz.php new file mode 100644 index 0000000..d7de7c5 --- /dev/null +++ b/includes/pear/Image/GraphViz.php @@ -0,0 +1,1005 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Image_GraphViz + * + * PHP version 4 and 5 + * + * Copyright (c) 2001-2007, Dr. Volker Göbbels <vmg@arachnion.de> and + * Sebastian Bergmann <sb@sebastian-bergmann.de>. All rights reserved. + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Image + * @package Image_GraphViz + * @author Dr. Volker Göbbels <vmg@arachnion.de> + * @author Sebastian Bergmann <sb@sebastian-bergmann.de> + * @author Karsten Dambekalns <k.dambekalns@fishfarm.de> + * @author Michael Lively Jr. <mlively@ft11.net> + * @author Philippe Jausions <Philippe.Jausions@11abacus.com> + * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de> + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: GraphViz.php 304688 2010-10-24 05:21:17Z clockwerx $ + * @link http://pear.php.net/package/Image_GraphViz + * @link http://www.graphviz.org/ + * @since File available since Release 0.1.0 + */ + +/** + * Required PEAR classes + */ +require_once 'System.php'; + +/** + * Interface to AT&T's GraphViz tools. + * + * The GraphViz class allows for the creation of and to work with directed + * and undirected graphs and their visualization with AT&T's GraphViz tools. + * + * <code> + * <?php + * require_once 'Image/GraphViz.php'; + * + * $graph = new Image_GraphViz(); + * + * $graph->addNode( + * 'Node1', + * array( + * 'URL' => 'http://link1', + * 'label' => 'This is a label', + * 'shape' => 'box' + * ) + * ); + * + * $graph->addNode( + * 'Node2', + * array( + * 'URL' => 'http://link2', + * 'fontsize' => '14' + * ) + * ); + * + * $graph->addNode( + * 'Node3', + * array( + * 'URL' => 'http://link3', + * 'fontsize' => '20' + * ) + * ); + * + * $graph->addEdge( + * array( + * 'Node1' => 'Node2' + * ), + * array( + * 'label' => 'Edge Label' + * ) + * ); + * + * $graph->addEdge( + * array( + * 'Node1' => 'Node2' + * ), + * array( + * 'color' => 'red' + * ) + * ); + * + * $graph->image(); + * ?> + * </code> + * + * @category Image + * @package Image_GraphViz + * @author Sebastian Bergmann <sb@sebastian-bergmann.de> + * @author Dr. Volker Göbbels <vmg@arachnion.de> + * @author Karsten Dambekalns <k.dambekalns@fishfarm.de> + * @author Michael Lively Jr. <mlively@ft11.net> + * @author Philippe Jausions <Philippe.Jausions@11abacus.com> + * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de> + * @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Image_GraphViz + * @link http://www.graphviz.org/ + * @since Class available since Release 0.1 + */ +class Image_GraphViz +{ + /** + * Base path to GraphViz commands + * + * @var string + */ + var $binPath = ''; + + /** + * Path to GraphViz/dot command + * + * @var string + */ + var $dotCommand = 'dot'; + + /** + * Path to GraphViz/neato command + * + * @var string + */ + var $neatoCommand = 'neato'; + + /** + * Representation of the graph + * + * @var array + */ + var $graph = array('edgesFrom' => array(), + 'nodes' => array(), + 'attributes' => array(), + 'directed' => true, + 'clusters' => array(), + 'subgraphs' => array(), + 'name' => 'G', + 'strict' => true, + ); + + /** + * Whether to return PEAR_Error instance on failures instead of FALSE + * + * @var boolean + */ + protected $_returnFalseOnError = true; + + /** + * Constructor. + * + * Setting the name of the Graph is useful for including multiple image + * maps on one page. If not set, the graph will be named 'G'. + * + * @param boolean $directed Directed (TRUE) or undirected (FALSE) graph. + * Note: You MUST pass a boolean, and not just + * an expression that evaluates to TRUE or + * FALSE (i.e. NULL, empty string, 0 will NOT + * work) + * @param array $attributes Attributes of the graph + * @param string $name Name of the Graph + * @param boolean $strict Whether to collapse multiple edges between + * same nodes + * @param boolean $returnError Set to TRUE to return PEAR_Error instances + * on failures instead of FALSE + */ + public function Image_GraphViz($directed = true, $attributes = array(), + $name = 'G', $strict = true, $returnError = false) + { + $this->setDirected($directed); + $this->setAttributes($attributes); + $this->graph['name'] = $name; + $this->graph['strict'] = (boolean)$strict; + + $this->_returnFalseOnError = !$returnError; + } + + /** + * Outputs image of the graph in a given format + * + * This methods send HTTP headers + * + * @param string $format Format of the output image. This may be one + * of the formats supported by GraphViz. + * @param string $command "dot" or "neato" + * + * @return boolean TRUE on success, FALSE or PEAR_Error otherwise + */ + public function image($format = 'svg', $command = null) + { + $file = $this->saveParsedGraph(); + if (!$file || PEAR::isError($file)) { + return $file; + } + + $outputfile = $file . '.' . $format; + + $rendered = $this->renderDotFile($file, $outputfile, $format, + $command); + if ($rendered !== true) { + return $rendered; + } + + $sendContentLengthHeader = true; + + switch (strtolower($format)) { + case 'gif': + case 'png': + case 'bmp': + case 'jpeg': + case 'tiff': + header('Content-Type: image/' . $format); + break; + + case 'tif': + header('Content-Type: image/tiff'); + break; + + case 'jpg': + header('Content-Type: image/jpeg'); + break; + + case 'ico': + header('Content-Type: image/x-icon'); + break; + + case 'wbmp': + header('Content-Type: image/vnd.wap.wbmp'); + break; + + case 'pdf': + header('Content-Type: application/pdf'); + break; + + case 'mif': + header('Content-Type: application/vnd.mif'); + break; + + case 'vrml': + header('Content-Type: application/x-vrml'); + break; + + case 'svg': + header('Content-Type: image/svg+xml'); + break; + + case 'plain': + case 'plain-ext': + header('Content-Type: text/plain'); + break; + + default: + header('Content-Type: application/octet-stream'); + $sendContentLengthHeader = false; + } + + if ($sendContentLengthHeader) { + header('Content-Length: ' . filesize($outputfile)); + } + + $return = true; + if (readfile($outputfile) === false) { + $return = false; + } + @unlink($outputfile); + + return $return; + } + + /** + * Returns image (data) of the graph in a given format. + * + * @param string $format Format of the output image. This may be one + * of the formats supported by GraphViz. + * @param string $command "dot" or "neato" + * + * @return string The image (data) created by GraphViz, FALSE or PEAR_Error + * on error + * @since Method available since Release 1.1.0 + */ + public function fetch($format = 'svg', $command = null) + { + $file = $this->saveParsedGraph(); + if (!$file || PEAR::isError($file)) { + return $file; + } + + $outputfile = $file . '.' . $format; + + $rendered = $this->renderDotFile($file, $outputfile, $format, + $command); + if ($rendered !== true) { + return $rendered; + } + + @unlink($file); + + $fp = fopen($outputfile, 'rb'); + + if (!$fp) { + if ($this->_returnFalseOnError) { + return false; + } + $error = PEAR::raiseError('Could not read rendered file'); + return $error; + } + + $data = fread($fp, filesize($outputfile)); + fclose($fp); + @unlink($outputfile); + + return $data; + } + + /** + * Renders a given dot file into a given format. + * + * @param string $dotfile The absolute path of the dot file to use. + * @param string $outputfile The absolute path of the file to save to. + * @param string $format Format of the output image. This may be one + * of the formats supported by GraphViz. + * @param string $command "dot" or "neato" + * + * @return boolean TRUE if the file was saved, FALSE or PEAR_Error + * otherwise. + */ + public function renderDotFile($dotfile, $outputfile, $format = 'svg', + $command = null) + { + if (!file_exists($dotfile)) { + if ($this->_returnFalseOnError) { + return false; + } + $error = PEAR::raiseError('Could not find dot file'); + return $error; + } + + $oldmtime = file_exists($outputfile) ? filemtime($outputfile) : 0; + + switch ($command) { + case 'dot': + case 'neato': + break; + default: + $command = $this->graph['directed'] ? 'dot' : 'neato'; + } + $command_orig = $command; + + $command = $this->binPath.(($command == 'dot') ? $this->dotCommand + : $this->neatoCommand); + + $command .= ' -T'.escapeshellarg($format) + .' -o'.escapeshellarg($outputfile) + .' '.escapeshellarg($dotfile) + .' 2>&1'; + exec($command, $msg, $return_val); + + clearstatcache(); + if (file_exists($outputfile) && filemtime($outputfile) > $oldmtime + && $return_val == 0) { + return true; + } elseif ($this->_returnFalseOnError) { + return false; + } + $error = PEAR::raiseError($command_orig.' command failed: ' + .implode("\n", $msg)); + return $error; + } + + /** + * Adds a cluster to the graph. + * + * A cluster is a subgraph with a rectangle around it. + * + * @param string $id ID. + * @param array $title Title. + * @param array $attributes Attributes of the cluster. + * @param string $group ID of group to nest cluster into + * + * @return void + * @see addSubgraph() + */ + public function addCluster($id, $title, $attributes = array(), $group = 'default') + { + $this->graph['clusters'][$id]['title'] = $title; + $this->graph['clusters'][$id]['attributes'] = $attributes; + $this->graph['clusters'][$id]['embedIn'] = $group; + } + + /** + * Adds a subgraph to the graph. + * + * @param string $id ID. + * @param array $title Title. + * @param array $attributes Attributes of the cluster. + * @param string $group ID of group to nest subgraph into + * + * @return void + */ + public function addSubgraph($id, $title, $attributes = array(), $group = 'default') + { + $this->graph['subgraphs'][$id]['title'] = $title; + $this->graph['subgraphs'][$id]['attributes'] = $attributes; + $this->graph['subgraphs'][$id]['embedIn'] = $group; + } + + /** + * Adds a note to the graph. + * + * @param string $name Name of the node. + * @param array $attributes Attributes of the node. + * @param string $group Group of the node. + * + * @return void + */ + public function addNode($name, $attributes = array(), $group = 'default') + { + $this->graph['nodes'][$group][$name] = $attributes; + } + + /** + * Removes a node from the graph. + * + * This method doesn't remove edges associated with the node. + * + * @param string $name Name of the node to be removed. + * @param string $group Group of the node. + * + * @return void + */ + public function removeNode($name, $group = 'default') + { + if (isset($this->graph['nodes'][$group][$name])) { + unset($this->graph['nodes'][$group][$name]); + } + } + + /** + * Adds an edge to the graph. + * + * Examples: + * <code> + * $g->addEdge(array('node1' => 'node2')); + * $attr = array( + * 'label' => '+1', + * 'style' => 'dashed', + * ); + * $g->addEdge(array('node3' => 'node4'), $attr); + * + * // With port specification + * $g->addEdge(array('node5' => 'node6'), $attr, array('node6' => 'portA')); + * $g->addEdge(array('node7' => 'node8'), null, array('node7' => 'portC', + * 'node8' => 'portD')); + * </code> + * + * @param array $edge Start => End node of the edge. + * @param array $attributes Attributes of the edge. + * @param array $ports Start node => port, End node => port + * + * @return integer an edge ID that can be used with {@link removeEdge()} + */ + public function addEdge($edge, $attributes = array(), $ports = array()) + { + if (!is_array($edge)) { + return; + } + + $from = key($edge); + $to = $edge[$from]; + $info = array(); + + if (is_array($ports)) { + if (array_key_exists($from, $ports)) { + $info['portFrom'] = $ports[$from]; + } + + if (array_key_exists($to, $ports)) { + $info['portTo'] = $ports[$to]; + } + } + + if (is_array($attributes)) { + $info['attributes'] = $attributes; + } + + if (!empty($this->graph['strict'])) { + if (!isset($this->graph['edgesFrom'][$from][$to][0])) { + $this->graph['edgesFrom'][$from][$to][0] = $info; + } else { + $this->graph['edgesFrom'][$from][$to][0] = array_merge($this->graph['edgesFrom'][$from][$to][0], $info); + } + } else { + $this->graph['edgesFrom'][$from][$to][] = $info; + } + + return count($this->graph['edgesFrom'][$from][$to]) - 1; + } + + /** + * Removes an edge from the graph. + * + * @param array $edge Start and End node of the edge to be removed. + * @param integer $id specific edge ID (only usefull when multiple edges + * exist between the same 2 nodes) + * + * @return void + */ + public function removeEdge($edge, $id = null) + { + if (!is_array($edge)) { + return; + } + + $from = key($edge); + $to = $edge[$from]; + + if (!is_null($id)) { + if (isset($this->graph['edgesFrom'][$from][$to][$id])) { + unset($this->graph['edgesFrom'][$from][$to][$id]); + + if (count($this->graph['edgesFrom'][$from][$to]) == 0) { + unset($this->graph['edgesFrom'][$from][$to]); + } + } + } elseif (isset($this->graph['edgesFrom'][$from][$to])) { + unset($this->graph['edgesFrom'][$from][$to]); + } + } + + /** + * Adds attributes to the graph. + * + * @param array $attributes Attributes to be added to the graph. + * + * @return void + */ + public function addAttributes($attributes) + { + if (is_array($attributes)) { + $this->graph['attributes'] = array_merge($this->graph['attributes'], $attributes); + } + } + + /** + * Sets attributes of the graph. + * + * @param array $attributes Attributes to be set for the graph. + * + * @return void + */ + public function setAttributes($attributes) + { + if (is_array($attributes)) { + $this->graph['attributes'] = $attributes; + } + } + + /** + * Escapes an (attribute) array + * + * Detects if an attribute is <html>, contains double-quotes, etc... + * + * @param array $input input to escape + * + * @return array input escaped + */ + protected function _escapeArray($input) + { + $output = array(); + + foreach ((array)$input as $k => $v) { + switch ($k) { + case 'label': + case 'headlabel': + case 'taillabel': + $v = $this->_escape($v, true); + break; + default: + $v = $this->_escape($v); + $k = $this->_escape($k); + } + + $output[$k] = $v; + } + + return $output; + } + + /** + * Returns a safe "ID" in DOT syntax + * + * @param string $input string to use as "ID" + * @param boolean $html whether to attempt detecting HTML-like content + * + * @return string + */ + protected function _escape($input, $html = false) + { + switch (strtolower($input)) { + case 'node': + case 'edge': + case 'graph': + case 'digraph': + case 'subgraph': + case 'strict': + return '"'.$input.'"'; + } + + if (is_bool($input)) { + return ($input) ? 'true' : 'false'; + } + + if ($html && (strpos($input, '</') !== false + || strpos($input, '/>') !== false)) { + return '<'.$input.'>'; + } + + if (preg_match('/^([a-z_][a-z_0-9]*|-?(\.[0-9]+|[0-9]+(\.[0-9]*)?))$/i', + $input)) { + return $input; + } + + return '"'.str_replace(array("\r\n", "\n", "\r", '"'), + array('\n', '\n', '\n', '\"'), $input).'"'; + } + + /** + * Sets directed/undirected flag for the graph. + * + * Note: You MUST pass a boolean, and not just an expression that evaluates + * to TRUE or FALSE (i.e. NULL, empty string, 0 will not work) + * + * @param boolean $directed Directed (TRUE) or undirected (FALSE) graph. + * + * @return void + */ + public function setDirected($directed) + { + if (is_bool($directed)) { + $this->graph['directed'] = $directed; + } + } + + /** + * Loads a graph from a file in Image_GraphViz format + * + * @param string $file File to load graph from. + * + * @return void + */ + public function load($file) + { + if ($serializedGraph = implode('', @file($file))) { + $g = unserialize($serializedGraph); + + if (!is_array($g)) { + return; + } + + // Convert old storage format to new one + $defaults = array('edgesFrom' => array(), + 'nodes' => array(), + 'attributes' => array(), + 'directed' => true, + 'clusters' => array(), + 'subgraphs' => array(), + 'name' => 'G', + 'strict' => true, + ); + + $this->graph = array_merge($defaults, $g); + + if (isset($this->graph['edges'])) { + foreach ($this->graph['edges'] as $id => $nodes) { + $attr = (isset($this->graph['edgeAttributes'][$id])) + ? $this->graph['edgeAttributes'][$id] + : array(); + + $this->addEdge($nodes, $attr); + } + + unset($this->graph['edges']); + unset($this->graph['edgeAttributes']); + } + } + } + + /** + * Save graph to file in Image_GraphViz format + * + * This saves the serialized version of the instance, not the + * rendered graph. + * + * @param string $file File to save the graph to. + * + * @return string File the graph was saved to, FALSE or PEAR_Error on + * failure. + */ + public function save($file = '') + { + $serializedGraph = serialize($this->graph); + + if (empty($file)) { + $file = System::mktemp('graph_'); + } + + if ($fp = @fopen($file, 'wb')) { + @fputs($fp, $serializedGraph); + @fclose($fp); + + return $file; + } + + if ($this->_returnFalseOnError) { + return false; + } + $error = PEAR::raiseError('Could not save serialized graph instance'); + return $error; + } + + /** + * Returns a list of sub-groups for a given parent group + * + * @param string $parent Group ID + * + * @return array list of group IDs + */ + protected function _getSubgraphs($parent) + { + $subgraphs = array(); + foreach ($this->graph['clusters'] as $id => $info) { + if ($info['embedIn'] === $parent) { + $subgraphs[] = $id; + } + } + foreach ($this->graph['subgraphs'] as $id => $info) { + if ($info['embedIn'] === $parent) { + $subgraphs[] = $id; + } + } + return $subgraphs; + } + + /** + * Returns a list of cluster/subgraph IDs + * + * @return array + */ + protected function _getGroups() + { + $groups = array_merge(array_keys($this->graph['clusters']), + array_keys($this->graph['subgraphs'])); + return array_unique($groups); + } + + /** + * Returns a list of top groups + * + * @return array + */ + protected function _getTopGraphs() + { + $top = array(); + $groups = $this->_getGroups(); + + foreach ($groups as $id) { + $isTop = ($id === 'default'); + if (isset($this->graph['clusters'][$id]) + && $this->graph['clusters'][$id]['embedIn'] === 'default') { + $isTop = true; + } + if (isset($this->graph['subgraphs'][$id]) + && $this->graph['subgraphs'][$id]['embedIn'] === 'default') { + $isTop = true; + } + if ($isTop) { + $top[] = $id; + } + } + + return array_unique($top); + } + + /** + * Parses the graph into GraphViz markup. + * + * @return string GraphViz markup + */ + public function parse() + { + $parsedGraph = (empty($this->graph['strict'])) ? '' : 'strict '; + $parsedGraph .= (empty($this->graph['directed'])) ? 'graph ' : 'digraph '; + $parsedGraph .= $this->_escape($this->graph['name'])." {\n"; + + $indent = ' '; + + $attr = $this->_escapeArray($this->graph['attributes']); + + foreach ($attr as $key => $value) { + $parsedGraph .= $indent.$key.'='.$value.";\n"; + } + + $groups = $this->_getGroups(); + foreach ($this->graph['nodes'] as $group => $nodes) { + if (!in_array($group, $groups)) { + $parsedGraph .= $this->_nodes($nodes, $indent); + } + } + $tops = $this->_getTopGraphs(); + foreach ($tops as $group) { + $parsedGraph .= $this->_subgraph($group, $indent); + } + + if (!empty($this->graph['directed'])) { + $separator = ' -> '; + } else { + $separator = ' -- '; + } + + foreach ($this->graph['edgesFrom'] as $from => $toNodes) { + $from = $this->_escape($from); + + foreach ($toNodes as $to => $edges) { + $to = $this->_escape($to); + + foreach ($edges as $info) { + $f = $from; + $t = $to; + + if (array_key_exists('portFrom', $info)) { + $f .= ':'.$this->_escape($info['portFrom']); + } + + if (array_key_exists('portTo', $info)) { + $t .= ':'.$this->_escape($info['portTo']); + } + + $parsedGraph .= $indent.$f.$separator.$t; + + if (!empty($info['attributes'])) { + $attributeList = array(); + + foreach ($this->_escapeArray($info['attributes']) as $key => $value) { + switch ($key) { + case 'lhead': + case 'ltail': + if (strncasecmp($value, 'cluster', 7)) { + $value = 'cluster_'.$value; + } + break; + } + $attributeList[] = $key.'='.$value; + } + + $parsedGraph .= ' [ '.implode(',', $attributeList).' ]'; + } + + $parsedGraph .= ";\n"; + } + } + } + + return $parsedGraph . "}\n"; + } + + /** + * Output nodes + * + * @param array $nodes nodes list + * @param string $indent space indentation + * + * @return string output + */ + protected function _nodes($nodes, $indent) + { + $parsedGraph = ''; + foreach ($nodes as $node => $attributes) { + $parsedGraph .= $indent.$this->_escape($node); + + $attributeList = array(); + + foreach ($this->_escapeArray($attributes) as $key => $value) { + $attributeList[] = $key.'='.$value; + } + + if (!empty($attributeList)) { + $parsedGraph .= ' [ '.implode(',', $attributeList).' ]'; + } + + $parsedGraph .= ";\n"; + } + return $parsedGraph; + } + + /** + * Generates output for a group + * + * @return string output + */ + protected function _subgraph($group, &$indent) + { + $parsedGraph = ''; + $nodes = $this->graph['nodes'][$group]; + + if ($group !== 'default') { + $type = null; + $_group = $this->_escape($group); + + if (isset($this->graph['clusters'][$group])) { + $type = 'clusters'; + if (strncasecmp($group, 'cluster', 7)) { + $_group = $this->_escape('cluster_'.$group); + } + } elseif (isset($this->graph['subgraphs'][$group])) { + $type = 'subgraphs'; + } + $parsedGraph .= $indent.'subgraph '.$_group." {\n"; + + $indent .= ' '; + + if ($type !== null && isset($this->graph[$type][$group])) { + $cluster = $this->graph[$type][$group]; + $_attr = $this->_escapeArray($cluster['attributes']); + + $attr = array(); + foreach ($_attr as $key => $value) { + $attr[] = $key.'='.$value; + } + + if (strlen($cluster['title'])) { + $attr[] = 'label=' + .$this->_escape($cluster['title'], true); + } + + if ($attr) { + $parsedGraph .= $indent.'graph [ '.implode(',', $attr) + ." ];\n"; + } + } + } + + $parsedGraph .= $this->_nodes($nodes, $indent); + + foreach ($this->_getSubgraphs($group) as $_group) { + $parsedGraph .= $this->_subgraph($_group, $indent); + } + + if ($group !== 'default') { + $indent = substr($indent, 0, -4); + + $parsedGraph .= $indent."}\n"; + } + + return $parsedGraph; + } + + /** + * Saves GraphViz markup to file (in DOT language) + * + * @param string $file File to write the GraphViz markup to. + * + * @return string File to which the GraphViz markup was written, FALSE or + * or PEAR_Error on failure. + */ + public function saveParsedGraph($file = '') + { + $parsedGraph = $this->parse(); + + if (!empty($parsedGraph)) { + if (empty($file)) { + $file = System::mktemp('graph_'); + } + + if ($fp = @fopen($file, 'wb')) { + @fputs($fp, $parsedGraph); + @fclose($fp); + + return $file; + } + } + + if ($this->_returnFalseOnError) { + return false; + } + $error = PEAR::raiseError('Could not save graph'); + return $error; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/includes/pear/MIME/Type.php b/includes/pear/MIME/Type.php new file mode 100644 index 0000000..77491d2 --- /dev/null +++ b/includes/pear/MIME/Type.php @@ -0,0 +1,598 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +/** + * Part of MIME_Type + * + * PHP version 4 and 5 + * + * @category File + * @package MIME_Type + * @author Ian Eure <ieure@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL + * @link http://pear.php.net/package/MIME_Type + */ + +require_once 'PEAR.php'; + +$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd'); +$_fileCmd = 'file'; + +/** + * Class for working with MIME types + * + * @category MIME + * @package MIME_Type + * @author Ian Eure <ieure@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL + * @version Release: @version@ + * @link http://pear.php.net/package/MIME_Type + */ +class MIME_Type +{ + /** + * The MIME media type + * + * @var string + */ + var $media = ''; + + /** + * The MIME media sub-type + * + * @var string + */ + var $subType = ''; + + /** + * Optional MIME parameters + * + * @var array + */ + var $parameters = array(); + + /** + * List of valid media types. + * A media type is the string in front of the slash. + * The media type of "text/xml" would be "text". + * + * @var array + */ + var $validMediaTypes = array( + 'text', + 'image', + 'audio', + 'video', + 'application', + 'multipart', + 'message' + ); + + /** + * If the finfo functions shall be used when they are available + * + * @var boolean + */ + var $useFinfo = true; + + /** + * If mime_content_type shall be used when available + * + * @var boolean + */ + var $useMimeContentType = true; + + /** + * If the file command shall be used when available + * + * @var boolean + */ + var $useFileCmd = true; + + /** + * If the in-built file extension detection shall be used + * + * @var boolean + */ + var $useExtension = true; + + /** + * Path to the "magic" file database. + * If NULL, the default one is used + * + * @var string + */ + var $magicFile = null; + + + /** + * Constructor. + * + * If $type is set, if will be parsed and the appropriate class vars set. + * If not, you get an empty class. + * This is useful, but not quite as useful as parsing a type. + * + * @param string $type MIME type + * + * @return void + */ + function MIME_Type($type = false) + { + if ($type) { + $this->parse($type); + } + } + + + /** + * Parse a mime-type and set the class variables. + * + * @param string $type MIME type to parse + * + * @return boolean True if the type has been parsed, false if not + */ + function parse($type) + { + if ($type instanceof PEAR_Error) { + return false; + } + + $this->media = $this->getMedia($type); + $this->subType = $this->getSubType($type); + $this->parameters = array(); + if (MIME_Type::hasParameters($type)) { + include_once 'MIME/Type/Parameter.php'; + foreach (MIME_Type::getParameters($type) as $param) { + $param = new MIME_Type_Parameter($param); + $this->parameters[$param->name] = $param; + } + } + + return true; + } + + + /** + * Does this type have any parameters? + * + * @param string $type MIME type to check + * + * @return boolean true if $type has parameters, false otherwise + * @static + */ + function hasParameters($type) + { + if (strstr($type, ';')) { + return true; + } + return false; + } + + + /** + * Get a MIME type's parameters + * + * @param string $type MIME type to get parameters of + * + * @return array $type's parameters + * @static + */ + function getParameters($type) + { + $params = array(); + $tmp = explode(';', $type); + for ($i = 1; $i < count($tmp); $i++) { + $params[] = trim($tmp[$i]); + } + return $params; + } + + + /** + * Strip parameters from a MIME type string. + * + * @param string $type MIME type string + * + * @return string MIME type with parameters removed + * @static + */ + function stripParameters($type) + { + if (strstr($type, ';')) { + return substr($type, 0, strpos($type, ';')); + } + return $type; + } + + + /** + * Removes comments from a media type, subtype or parameter. + * + * @param string $string String to strip comments from + * @param string &$comment Comment is stored in there. + * Do not set it to NULL if you want the comment. + * + * @return string String without comments + * @static + */ + function stripComments($string, &$comment) + { + if (strpos($string, '(') === false) { + return $string; + } + + $inquote = false; + $escaped = false; + $incomment = 0; + $newstring = ''; + + for ($n = 0; $n < strlen($string); $n++) { + if ($escaped) { + if ($incomment == 0) { + $newstring .= $string[$n]; + } else if ($comment !== null) { + $comment .= $string[$n]; + } + $escaped = false; + } else if ($string[$n] == '\\') { + $escaped = true; + } else if (!$inquote && $incomment > 0 && $string[$n] == ')') { + $incomment--; + if ($incomment == 0 && $comment !== null) { + $comment .= ' '; + } + } else if (!$inquote && $string[$n] == '(') { + $incomment++; + } else if ($string[$n] == '"') { + if ($inquote) { + $inquote = false; + } else { + $inquote = true; + } + } else if ($incomment == 0) { + $newstring .= $string[$n]; + } else if ($comment !== null) { + $comment .= $string[$n]; + } + } + + if ($comment !== null) { + $comment = trim($comment); + } + + return $newstring; + } + + + /** + * Get a MIME type's media + * Note: 'media' refers to the portion before the first slash + * + * @param string $type MIME type to get media of + * + * @return string $type's media + * @static + */ + function getMedia($type) + { + $tmp = explode('/', $type); + return strtolower(trim(MIME_Type::stripComments($tmp[0], $null))); + } + + + /** + * Get a MIME type's subtype + * + * @param string $type MIME type to get subtype of + * + * @return string $type's subtype, null if invalid mime type + * @static + */ + function getSubType($type) + { + $tmp = explode('/', $type); + if (!isset($tmp[1])) { + return null; + } + $tmp = explode(';', $tmp[1]); + return strtolower(trim(MIME_Type::stripComments($tmp[0], $null))); + } + + + /** + * Create a textual MIME type from object values + * + * This function performs the opposite function of parse(). + * + * @return string MIME type string + */ + function get() + { + $type = strtolower($this->media . '/' . $this->subType); + if (count($this->parameters)) { + foreach ($this->parameters as $key => $null) { + $type .= '; ' . $this->parameters[$key]->get(); + } + } + return $type; + } + + + /** + * Is this type experimental? + * + * Note: Experimental types are denoted by a leading 'x-' in the media or + * subtype, e.g. text/x-vcard or x-world/x-vrml. + * + * @param string $type MIME type to check + * + * @return boolean true if $type is experimental, false otherwise + * @static + */ + function isExperimental($type) + { + if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-' + || substr(MIME_Type::getSubType($type), 0, 2) == 'x-' + ) { + return true; + } + return false; + } + + + /** + * Is this a vendor MIME type? + * + * Note: Vendor types are denoted with a leading 'vnd. in the subtype. + * + * @param string $type MIME type to check + * + * @return boolean true if $type is a vendor type, false otherwise + * @static + */ + function isVendor($type) + { + if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') { + return true; + } + return false; + } + + + /** + * Is this a wildcard type? + * + * @param string $type MIME type to check + * + * @return boolean true if $type is a wildcard, false otherwise + * @static + */ + function isWildcard($type) + { + if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') { + return true; + } + return false; + } + + + /** + * Perform a wildcard match on a MIME type + * + * Example: + * MIME_Type::wildcardMatch('image/*', 'image/png') + * + * @param string $card Wildcard to check against + * @param string $type MIME type to check + * + * @return boolean true if there was a match, false otherwise + * @static + */ + function wildcardMatch($card, $type) + { + if (!MIME_Type::isWildcard($card)) { + return false; + } + + if ($card == '*/*') { + return true; + } + + if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) { + return true; + } + + return false; + } + + + /** + * Add a parameter to this type + * + * @param string $name Attribute name + * @param string $value Attribute value + * @param string $comment Comment for this parameter + * + * @return void + */ + function addParameter($name, $value, $comment = false) + { + $tmp = new MIME_Type_Parameter(); + + $tmp->name = $name; + $tmp->value = $value; + $tmp->comment = $comment; + $this->parameters[$name] = $tmp; + } + + + /** + * Remove a parameter from this type + * + * @param string $name Parameter name + * + * @return void + */ + function removeParameter($name) + { + unset($this->parameters[$name]); + } + + + /** + * Autodetect a file's MIME-type + * + * This function may be called staticly. + * + * @param string $file Path to the file to get the type of + * @param bool $params Append MIME parameters if true + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + * + * @since 1.0.0beta1 + * @static + */ + function autoDetect($file, $params = false) + { + $isStatic = !(isset($this) && get_class($this) == __CLASS__); + if ($isStatic) { + $t = new MIME_Type(); + return $t->_autoDetect($file, $params); + } else { + $type = $this->_autoDetect($file, $params); + if (!$type instanceof PEAR_Error) { + $this->parse($type); + } + return $type; + } + } + + /** + * Autodetect a file's MIME-type + * + * @param string $file Path to the file to get the type of + * @param bool $params Append MIME parameters if true + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + * + * @since 1.3.0 + * + * @internal Tries to use fileinfo extension at first. If that + * does not work, mime_magic is used. If this is also not available + * or does not succeed, "file" command is tried to be executed with + * System_Command. When that fails, too, then we use our in-built + * extension-to-mimetype-mapping list. + */ + function _autoDetect($file, $params = false) + { + // Sanity checks + if (!file_exists($file)) { + return PEAR::raiseError("File \"$file\" doesn't exist"); + } + + if (!is_readable($file)) { + return PEAR::raiseError("File \"$file\" is not readable"); + } + + if ($this->useFinfo && function_exists('finfo_file')) { + $finfo = finfo_open(FILEINFO_MIME, $this->magicFile); + if ($finfo) { + $type = finfo_file($finfo, $file); + finfo_close($finfo); + if ($type !== false && $type !== '') { + return MIME_Type::_handleDetection($type, $params); + } + } + } + + if ($this->useMimeContentType && function_exists('mime_content_type')) { + $type = mime_content_type($file); + if ($type !== false && $type !== '') { + return MIME_Type::_handleDetection($type, $params); + } + } + + if ($this->useFileCmd) { + @include_once 'System/Command.php'; + if (class_exists('System_Command')) { + $type = MIME_Type::_fileAutoDetect($file); + if ($type !== false && $type !== '') { + return MIME_Type::_handleDetection($type, $params); + } + } + } + + if ($this->useExtension) { + include_once 'MIME/Type/Extension.php'; + $mte = new MIME_Type_Extension(); + return $mte->getMIMEType($file); + } + + return PEAR::raiseError("Sorry, couldn't determine file type."); + } + + + /** + * Handles a detected MIME type and modifies it if necessary. + * + * @param string $type MIME Type of a file + * @param bool $params Append MIME parameters if true + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + * @static + */ + function _handleDetection($type, $params) + { + // _fileAutoDetect() may have returned an error. + if (PEAR::isError($type)) { + return $type; + } + + // Don't return an empty string + if (!$type || !strlen($type)) { + return PEAR::raiseError("Sorry, couldn't determine file type."); + } + + // Strip parameters if present & requested + if (MIME_Type::hasParameters($type) && !$params) { + $type = MIME_Type::stripParameters($type); + } + + return $type; + } + + + /** + * Autodetect a file's MIME-type with 'file' and System_Command + * + * This function may be called staticly. + * + * @param string $file Path to the file to get the type of + * + * @return string $file's MIME-type + * + * @since 1.0.0beta1 + * @static + */ + function _fileAutoDetect($file) + { + $cmd = new System_Command(); + $magic = ''; + + // Make sure we have the 'file' command. + $fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd'); + if (!$cmd->which($fileCmd)) { + unset($cmd); + return PEAR::raiseError("Can't find file command \"{$fileCmd}\""); + } + if (strlen($this->magicFile)) { + $magic = '--magic-file ' . escapeshellarg($this->magicFile); + } + + $cmd->pushCommand($fileCmd, $magic, "-bi " . escapeshellarg($file)); + $res = $cmd->execute(); + unset($cmd); + + return $res; + } + +}
\ No newline at end of file diff --git a/includes/pear/MIME/Type/Extension.php b/includes/pear/MIME/Type/Extension.php new file mode 100644 index 0000000..7f3da7a --- /dev/null +++ b/includes/pear/MIME/Type/Extension.php @@ -0,0 +1,292 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +/** + * Part of MIME_Type + * + * PHP version 4 and 5 + * + * @category File + * @package MIME_Type + * @author Christian Schmidt <schmidt@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL + * @link http://pear.php.net/package/MIME_Type + */ + +require_once 'PEAR.php'; + +/** + * Class for mapping file extensions to MIME types. + * + * @category MIME + * @package MIME_Type + * @author Christian Schmidt <schmidt@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL + * @version Release: @version@ + * @link http://pear.php.net/package/MIME_Type + */ +class MIME_Type_Extension +{ + /** + * Mapping between file extension and MIME type. + * + * @internal The array is sorted alphabetically by value and with primary + * extension first. Be careful about not adding duplicate keys - PHP + * silently ignores duplicates. The following command can be used for + * checking for duplicates: + * grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d + * application/octet-stream is generally used as fallback when no other + * MIME-type can be found, but the array does not contain a lot of such + * unknown extension. One entry exists, though, to allow detection of + * file extension for this MIME-type. + * + * @var array + */ + var $extensionToType = array ( + 'ez' => 'application/andrew-inset', + 'atom' => 'application/atom+xml', + 'jar' => 'application/java-archive', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'mathml' => 'application/mathml+xml', + 'doc' => 'application/msword', + 'dat' => 'application/octet-stream', + 'oda' => 'application/oda', + 'ogg' => 'application/ogg', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'rdf' => 'application/rdf+xml', + 'rss' => 'application/rss+xml', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'gram' => 'application/srgs', + 'grxml' => 'application/srgs+xml', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'mif' => 'application/vnd.mif', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xls' => 'application/vnd.ms-excel', + 'xlb' => 'application/vnd.ms-excel', + 'xlt' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pps' => 'application/vnd.ms-powerpoint', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'vsd' => 'application/vnd.visio', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'vxml' => 'application/voicexml+xml', + 'bcpio' => 'application/x-bcpio', + 'vcd' => 'application/x-cdlink', + 'pgn' => 'application/x-chess-pgn', + 'cpio' => 'application/x-cpio', + 'csh' => 'application/x-csh', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'spl' => 'application/x-futuresplash', + 'tgz' => 'application/x-gtar', + 'gtar' => 'application/x-gtar', + 'hdf' => 'application/x-hdf', + 'js' => 'application/x-javascript', + 'skp' => 'application/x-koan', + 'skd' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'latex' => 'application/x-latex', + 'nc' => 'application/x-netcdf', + 'cdf' => 'application/x-netcdf', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'texinfo' => 'application/x-texinfo', + 'texi' => 'application/x-texinfo', + 't' => 'application/x-troff', + 'tr' => 'application/x-troff', + 'roff' => 'application/x-troff', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'ms' => 'application/x-troff-ms', + 'ustar' => 'application/x-ustar', + 'src' => 'application/x-wais-source', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'xslt' => 'application/xslt+xml', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'dtd' => 'application/xml-dtd', + 'zip' => 'application/zip', + 'au' => 'audio/basic', + 'snd' => 'audio/basic', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'kar' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'm3u' => 'audio/x-mpegurl', + 'wma' => 'audio/x-ms-wma', + 'wax' => 'audio/x-ms-wax', + 'ram' => 'audio/x-pn-realaudio', + 'ra' => 'audio/x-pn-realaudio', + 'rm' => 'application/vnd.rn-realmedia', + 'wav' => 'audio/x-wav', + 'pdb' => 'chemical/x-pdb', + 'xyz' => 'chemical/x-xyz', + 'bmp' => 'image/bmp', + 'cgm' => 'image/cgm', + 'gif' => 'image/gif', + 'ief' => 'image/ief', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'svg' => 'image/svg+xml', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'djvu' => 'image/vnd.djvu', + 'djv' => 'image/vnd.djvu', + 'wbmp' => 'image/vnd.wap.wbmp', + 'ras' => 'image/x-cmu-raster', + 'ico' => 'image/x-icon', + 'pnm' => 'image/x-portable-anymap', + 'pbm' => 'image/x-portable-bitmap', + 'pgm' => 'image/x-portable-graymap', + 'ppm' => 'image/x-portable-pixmap', + 'rgb' => 'image/x-rgb', + 'xbm' => 'image/x-xbitmap', + 'psd' => 'image/x-photoshop', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'eml' => 'message/rfc822', + 'igs' => 'model/iges', + 'iges' => 'model/iges', + 'msh' => 'model/mesh', + 'mesh' => 'model/mesh', + 'silo' => 'model/mesh', + 'wrl' => 'model/vrml', + 'vrml' => 'model/vrml', + 'ics' => 'text/calendar', + 'ifb' => 'text/calendar', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'html' => 'text/html', + 'htm' => 'text/html', + 'txt' => 'text/plain', + 'asc' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'sgml' => 'text/sgml', + 'sgm' => 'text/sgml', + 'tsv' => 'text/tab-separated-values', + 'wml' => 'text/vnd.wap.wml', + 'wmls' => 'text/vnd.wap.wmlscript', + 'etx' => 'text/x-setext', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'mxu' => 'video/vnd.mpegurl', + 'm4u' => 'video/vnd.mpegurl', + 'flv' => 'video/x-flv', + 'asf' => 'video/x-ms-asf', + 'asx' => 'video/x-ms-asf', + 'wmv' => 'video/x-ms-wmv', + 'wm' => 'video/x-ms-wm', + 'wmx' => 'video/x-ms-wmx', + 'avi' => 'video/x-msvideo', + 'ogv' => 'video/ogg', + 'movie' => 'video/x-sgi-movie', + 'ice' => 'x-conference/x-cooltalk', + ); + + + + /** + * Autodetect a file's MIME-type. + * + * @param string $file Path to the file to get the type of + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + */ + function getMIMEType($file) + { + $extension = substr(strrchr($file, '.'), 1); + if ($extension === false) { + return PEAR::raiseError("File has no extension."); + } + + if (!isset($this->extensionToType[$extension])) { + return PEAR::raiseError("Sorry, couldn't determine file type."); + } + + return $this->extensionToType[$extension]; + } + + + + /** + * Return default MIME-type for the specified extension. + * + * @param string $type MIME-type + * + * @return string A file extension without leading period. + */ + function getExtension($type) + { + include_once 'MIME/Type.php'; + // Strip parameters and comments. + $type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type); + + $extension = array_search($type, $this->extensionToType); + if ($extension === false) { + return PEAR::raiseError("Sorry, couldn't determine extension."); + } + return $extension; + } + +} + +?>
\ No newline at end of file diff --git a/includes/pear/MIME/Type/Parameter.php b/includes/pear/MIME/Type/Parameter.php new file mode 100644 index 0000000..759fd58 --- /dev/null +++ b/includes/pear/MIME/Type/Parameter.php @@ -0,0 +1,170 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +/** + * Part of MIME_Type + * + * PHP version 4 and 5 + * + * @category File + * @package MIME_Type + * @author Ian Eure <ieure@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL + * @link http://pear.php.net/package/MIME_Type + */ + +/** + * Class for working with MIME type parameters + * + * @category File + * @package MIME_Type + * @author Ian Eure <ieure@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL + * @version Release: @version@ + * @link http://pear.php.net/package/MIME_Type + */ +class MIME_Type_Parameter +{ + /** + * Parameter name + * + * @var string + */ + var $name; + + /** + * Parameter value + * + * @var string + */ + var $value; + + /** + * Parameter comment + * + * @var string + */ + var $comment; + + + /** + * Constructor. + * + * @param string $param MIME parameter to parse, if set. + * + * @return void + */ + function MIME_Type_Parameter($param = false) + { + if ($param) { + $this->parse($param); + } + } + + + /** + * Parse a MIME type parameter and set object fields + * + * @param string $param MIME type parameter to parse + * + * @return void + */ + function parse($param) + { + $comment = ''; + $param = MIME_Type::stripComments($param, $comment); + $this->name = $this->getAttribute($param); + $this->value = $this->getValue($param); + $this->comment = $comment; + } + + + /** + * Get a parameter attribute (e.g. name) + * + * @param string $param MIME type parameter + * + * @return string Attribute name + * @static + */ + function getAttribute($param) + { + $tmp = explode('=', $param); + return trim($tmp[0]); + } + + + /** + * Get a parameter value + * + * @param string $param MIME type parameter + * + * @return string Value + * @static + */ + function getValue($param) + { + $tmp = explode('=', $param, 2); + $value = $tmp[1]; + $value = trim($value); + if ($value[0] == '"' && $value[strlen($value)-1] == '"') { + $value = substr($value, 1, -1); + } + $value = str_replace('\\"', '"', $value); + return $value; + } + + + /** + * Get a parameter comment + * + * @param string $param MIME type parameter + * + * @return string Parameter comment + * @see hasComment() + * @static + */ + function getComment($param) + { + $cs = strpos($param, '('); + if ($cs === false) { + return null; + } + $comment = substr($param, $cs); + return trim($comment, '() '); + } + + + /** + * Does this parameter have a comment? + * + * @param string $param MIME type parameter + * + * @return boolean true if $param has a comment, false otherwise + * @static + */ + function hasComment($param) + { + if (strstr($param, '(')) { + return true; + } + return false; + } + + + /** + * Get a string representation of this parameter + * + * This function performs the oppsite of parse() + * + * @return string String representation of parameter + */ + function get() + { + $val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"'; + if ($this->comment) { + $val .= ' (' . $this->comment . ')'; + } + return $val; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/OS/Guess.php b/includes/pear/OS/Guess.php new file mode 100644 index 0000000..6f41f32 --- /dev/null +++ b/includes/pear/OS/Guess.php @@ -0,0 +1,338 @@ +<?php +/** + * The OS_Guess class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Gregory Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since PEAR 0.1 + */ + +// {{{ uname examples + +// php_uname() without args returns the same as 'uname -a', or a PHP-custom +// string for Windows. +// PHP versions prior to 4.3 return the uname of the host where PHP was built, +// as of 4.3 it returns the uname of the host running the PHP code. +// +// PC RedHat Linux 7.1: +// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown +// +// PC Debian Potato: +// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown +// +// PC FreeBSD 3.3: +// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.3: +// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5 w/uname from GNU shellutils: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown +// +// HP 9000/712 HP-UX 10: +// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license +// +// HP 9000/712 HP-UX 10 w/uname from GNU shellutils: +// HP-UX host B.10.10 A 9000/712 unknown +// +// IBM RS6000/550 AIX 4.3: +// AIX host 3 4 000003531C00 +// +// AIX 4.3 w/uname from GNU shellutils: +// AIX host 3 4 000003531C00 unknown +// +// SGI Onyx IRIX 6.5 w/uname from GNU shellutils: +// IRIX64 host 6.5 01091820 IP19 mips +// +// SGI Onyx IRIX 6.5: +// IRIX64 host 6.5 01091820 IP19 +// +// SparcStation 20 Solaris 8 w/uname from GNU shellutils: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc +// +// SparcStation 20 Solaris 8: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20 +// +// Mac OS X (Darwin) +// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh +// +// Mac OS X early versions +// + +// }}} + +/* TODO: + * - define endianness, to allow matchSignature("bigend") etc. + */ + +/** + * Retrieves information about the current operating system + * + * This class uses php_uname() to grok information about the current OS + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Gregory Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class OS_Guess +{ + var $sysname; + var $nodename; + var $cpu; + var $release; + var $extra; + + function OS_Guess($uname = null) + { + list($this->sysname, + $this->release, + $this->cpu, + $this->extra, + $this->nodename) = $this->parseSignature($uname); + } + + function parseSignature($uname = null) + { + static $sysmap = array( + 'HP-UX' => 'hpux', + 'IRIX64' => 'irix', + ); + static $cpumap = array( + 'i586' => 'i386', + 'i686' => 'i386', + 'ppc' => 'powerpc', + ); + if ($uname === null) { + $uname = php_uname(); + } + $parts = preg_split('/\s+/', trim($uname)); + $n = count($parts); + + $release = $machine = $cpu = ''; + $sysname = $parts[0]; + $nodename = $parts[1]; + $cpu = $parts[$n-1]; + $extra = ''; + if ($cpu == 'unknown') { + $cpu = $parts[$n - 2]; + } + + switch ($sysname) { + case 'AIX' : + $release = "$parts[3].$parts[2]"; + break; + case 'Windows' : + switch ($parts[1]) { + case '95/98': + $release = '9x'; + break; + default: + $release = $parts[1]; + break; + } + $cpu = 'i386'; + break; + case 'Linux' : + $extra = $this->_detectGlibcVersion(); + // use only the first two digits from the kernel version + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + case 'Mac' : + $sysname = 'darwin'; + $nodename = $parts[2]; + $release = $parts[3]; + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + break; + case 'Darwin' : + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + default: + $release = preg_replace('/-.*/', '', $parts[2]); + break; + } + + if (isset($sysmap[$sysname])) { + $sysname = $sysmap[$sysname]; + } else { + $sysname = strtolower($sysname); + } + if (isset($cpumap[$cpu])) { + $cpu = $cpumap[$cpu]; + } + return array($sysname, $release, $cpu, $extra, $nodename); + } + + function _detectGlibcVersion() + { + static $glibc = false; + if ($glibc !== false) { + return $glibc; // no need to run this multiple times + } + $major = $minor = 0; + include_once "System.php"; + // Use glibc's <features.h> header file to + // get major and minor version number: + if (@file_exists('/usr/include/features.h') && + @is_readable('/usr/include/features.h')) { + if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { + $features_file = fopen('/usr/include/features.h', 'rb'); + while (!feof($features_file)) { + $line = fgets($features_file, 8192); + if (!$line || (strpos($line, '#define') === false)) { + continue; + } + if (strpos($line, '__GLIBC__')) { + // major version number #define __GLIBC__ version + $line = preg_split('/\s+/', $line); + $glibc_major = trim($line[2]); + if (isset($glibc_minor)) { + break; + } + continue; + } + + if (strpos($line, '__GLIBC_MINOR__')) { + // got the minor version number + // #define __GLIBC_MINOR__ version + $line = preg_split('/\s+/', $line); + $glibc_minor = trim($line[2]); + if (isset($glibc_major)) { + break; + } + continue; + } + } + fclose($features_file); + if (!isset($glibc_major) || !isset($glibc_minor)) { + return $glibc = ''; + } + return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ; + } // no cpp + + $tmpfile = System::mktemp("glibctest"); + $fp = fopen($tmpfile, "w"); + fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n"); + fclose($fp); + $cpp = popen("/usr/bin/cpp $tmpfile", "r"); + while ($line = fgets($cpp, 1024)) { + if ($line{0} == '#' || trim($line) == '') { + continue; + } + + if (list($major, $minor) = explode(' ', trim($line))) { + break; + } + } + pclose($cpp); + unlink($tmpfile); + } // features.h + + if (!($major && $minor) && @is_link('/lib/libc.so.6')) { + // Let's try reading the libc.so.6 symlink + if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) { + list($major, $minor) = explode('.', $matches[1]); + } + } + + if (!($major && $minor)) { + return $glibc = ''; + } + + return $glibc = "glibc{$major}.{$minor}"; + } + + function getSignature() + { + if (empty($this->extra)) { + return "{$this->sysname}-{$this->release}-{$this->cpu}"; + } + return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}"; + } + + function getSysname() + { + return $this->sysname; + } + + function getNodename() + { + return $this->nodename; + } + + function getCpu() + { + return $this->cpu; + } + + function getRelease() + { + return $this->release; + } + + function getExtra() + { + return $this->extra; + } + + function matchSignature($match) + { + $fragments = is_array($match) ? $match : explode('-', $match); + $n = count($fragments); + $matches = 0; + if ($n > 0) { + $matches += $this->_matchFragment($fragments[0], $this->sysname); + } + if ($n > 1) { + $matches += $this->_matchFragment($fragments[1], $this->release); + } + if ($n > 2) { + $matches += $this->_matchFragment($fragments[2], $this->cpu); + } + if ($n > 3) { + $matches += $this->_matchFragment($fragments[3], $this->extra); + } + return ($matches == $n); + } + + function _matchFragment($fragment, $value) + { + if (strcspn($fragment, '*?') < strlen($fragment)) { + $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/'; + return preg_match($reg, $value); + } + return ($fragment == '*' || !strcasecmp($fragment, $value)); + } + +} +/* + * Local Variables: + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */
\ No newline at end of file diff --git a/includes/pear/PEAR.php b/includes/pear/PEAR.php new file mode 100644 index 0000000..48830fd --- /dev/null +++ b/includes/pear/PEAR.php @@ -0,0 +1,1040 @@ +<?php +/** + * PEAR, the PHP Extension and Application Repository + * + * PEAR class and PEAR_Error class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Sterling Hughes <sterling@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2010 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + /** + * Whether to enable internal debug messages. + * + * @var bool + */ + private $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + */ + private $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + */ + private $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + */ + private $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + */ + private $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + */ + private $_expected_errors = array(); + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @return void + */ + public function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + + if ($error_class !== null) { + $this->_error_class = $error_class; + } + + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @return void + */ + public function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + public static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + public static function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @return bool true if parameter is an error + */ + public static function isError($data, $code = null) + { + if (!is_object($data)) { + return false; + } + if (!is_a($data, 'PEAR_Error')) { + return false; + } + + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } + + return $data->getCode() == $code; + } + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + public function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + */ + public function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return count($this->_expected_errors); + } + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + public function popExpect() + { + return array_pop($this->_expected_errors); + } + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @since PHP 4.3.0 + */ + private function _checkDelExpect($error_code) + { + $deleted = false; + foreach ($this->_expected_errors as $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + + return $deleted; + } + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @since PHP 4.3.0 + */ + public function delExpect($error_code) + { + $deleted = false; + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; we walk through it trying + // to unset all values + foreach ($error_code as $key => $error) { + $deleted = $this->_checkDelExpect($error) ? true : false; + } + + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } + + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + public function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if ( + isset($this) && + isset($this->_expected_errors) && + count($this->_expected_errors) > 0 && + count($exp = end($this->_expected_errors)) + ) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp)) + ) { + $mode = PEAR_ERROR_RETURN; + } + } + + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + + return $a; + } + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @return object a PEAR error object + * @see PEAR::raiseError + */ + public function &throwError($message = null, $code = null, $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } + + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + + public static function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + public static function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + public function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + public function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + public function loadExtension($ext) + { + if (extension_loaded($ext)) { + return true; + } + + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 || + ini_get('safe_mode') == 1 + ) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } +} + +if (PEAR_ZE2) { + include_once 'PEAR5.php'; +} + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR_ZE2) { + $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); + } else { + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); + } + + if ($destructLifoExists) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if ( + isset($GLOBALS['_PEAR_shutdown_funcs']) && + is_array($GLOBALS['_PEAR_shutdown_funcs']) && + !empty($GLOBALS['_PEAR_shutdown_funcs']) + ) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Gregory Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + */ + public function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + + if (PEAR_ZE2) { + $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); + } else { + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); + } + + if (!$skiptrace) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + + $this->level = $options; + $this->callback = null; + } + + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + + printf($format, $this->getMessage()); + } + + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + + if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + /** + * Get the error mode from an error object. + * + * @return int error mode + */ + public function getMode() + { + return $this->mode; + } + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + */ + public function getCallback() + { + return $this->callback; + } + + /** + * Get the error message from an error object. + * + * @return string full error message + */ + public function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + /** + * Get error code from an error object + * + * @return int error code + */ + public function getCode() + { + return $this->code; + } + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + */ + public function getType() + { + return get_class($this); + } + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + */ + public function getUserInfo() + { + return $this->userinfo; + } + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + */ + public function getDebugInfo() + { + return $this->getUserInfo(); + } + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + */ + public function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + public function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + public function __toString() + { + return $this->getMessage(); + } + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + */ + public function toString() + { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/includes/pear/PEAR/Autoloader.php b/includes/pear/PEAR/Autoloader.php new file mode 100644 index 0000000..b833dc6 --- /dev/null +++ b/includes/pear/PEAR/Autoloader.php @@ -0,0 +1,218 @@ +<?php +/** + * Class auto-loader + * + * PHP versions 4 + + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 + */ + +// /* vim: set expandtab tabstop=4 shiftwidth=4: */ + +if (!extension_loaded("overload")) { + // die hard without ext/overload + die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader"); +} + +/** + * Include for PEAR_Error and PEAR classes + */ +require_once "PEAR.php"; + +/** + * This class is for objects where you want to separate the code for + * some methods into separate classes. This is useful if you have a + * class with not-frequently-used methods that contain lots of code + * that you would like to avoid always parsing. + * + * The PEAR_Autoloader class provides autoloading and aggregation. + * The autoloading lets you set up in which classes the separated + * methods are found. Aggregation is the technique used to import new + * methods, an instance of each class providing separated methods is + * stored and called every time the aggregated method is called. + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 + */ +class PEAR_Autoloader extends PEAR +{ + // {{{ properties + + /** + * Map of methods and classes where they are defined + * + * @var array + * + * @access private + */ + var $_autoload_map = array(); + + /** + * Map of methods and aggregate objects + * + * @var array + * + * @access private + */ + var $_method_map = array(); + + // }}} + // {{{ addAutoload() + + /** + * Add one or more autoload entries. + * + * @param string $method which method to autoload + * + * @param string $classname (optional) which class to find the method in. + * If the $method parameter is an array, this + * parameter may be omitted (and will be ignored + * if not), and the $method parameter will be + * treated as an associative array with method + * names as keys and class names as values. + * + * @return void + * + * @access public + */ + function addAutoload($method, $classname = null) + { + if (is_array($method)) { + array_walk($method, create_function('$a,&$b', '$b = strtolower($b);')); + $this->_autoload_map = array_merge($this->_autoload_map, $method); + } else { + $this->_autoload_map[strtolower($method)] = $classname; + } + } + + // }}} + // {{{ removeAutoload() + + /** + * Remove an autoload entry. + * + * @param string $method which method to remove the autoload entry for + * + * @return bool TRUE if an entry was removed, FALSE if not + * + * @access public + */ + function removeAutoload($method) + { + $method = strtolower($method); + $ok = isset($this->_autoload_map[$method]); + unset($this->_autoload_map[$method]); + return $ok; + } + + // }}} + // {{{ addAggregateObject() + + /** + * Add an aggregate object to this object. If the specified class + * is not defined, loading it will be attempted following PEAR's + * file naming scheme. All the methods in the class will be + * aggregated, except private ones (name starting with an + * underscore) and constructors. + * + * @param string $classname what class to instantiate for the object. + * + * @return void + * + * @access public + */ + function addAggregateObject($classname) + { + $classname = strtolower($classname); + if (!class_exists($classname)) { + $include_file = preg_replace('/[^a-z0-9]/i', '_', $classname); + include_once $include_file; + } + $obj =& new $classname; + $methods = get_class_methods($classname); + foreach ($methods as $method) { + // don't import priviate methods and constructors + if ($method{0} != '_' && $method != $classname) { + $this->_method_map[$method] = $obj; + } + } + } + + // }}} + // {{{ removeAggregateObject() + + /** + * Remove an aggregate object. + * + * @param string $classname the class of the object to remove + * + * @return bool TRUE if an object was removed, FALSE if not + * + * @access public + */ + function removeAggregateObject($classname) + { + $ok = false; + $classname = strtolower($classname); + reset($this->_method_map); + while (list($method, $obj) = each($this->_method_map)) { + if (is_a($obj, $classname)) { + unset($this->_method_map[$method]); + $ok = true; + } + } + return $ok; + } + + // }}} + // {{{ __call() + + /** + * Overloaded object call handler, called each time an + * undefined/aggregated method is invoked. This method repeats + * the call in the right aggregate object and passes on the return + * value. + * + * @param string $method which method that was called + * + * @param string $args An array of the parameters passed in the + * original call + * + * @return mixed The return value from the aggregated method, or a PEAR + * error if the called method was unknown. + */ + function __call($method, $args, &$retval) + { + $method = strtolower($method); + if (empty($this->_method_map[$method]) && isset($this->_autoload_map[$method])) { + $this->addAggregateObject($this->_autoload_map[$method]); + } + if (isset($this->_method_map[$method])) { + $retval = call_user_func_array(array($this->_method_map[$method], $method), $args); + return true; + } + return false; + } + + // }}} +} + +overload("PEAR_Autoloader"); + +?> diff --git a/includes/pear/PEAR/Builder.php b/includes/pear/PEAR/Builder.php new file mode 100644 index 0000000..b4fb430 --- /dev/null +++ b/includes/pear/PEAR/Builder.php @@ -0,0 +1,518 @@ +<?php +/** + * PEAR_Builder for building PHP extensions (PECL packages) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * + * TODO: log output parameters in PECL command line + * TODO: msdev path in configuration + */ + +/** + * Needed for extending PEAR_Builder + */ +require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; + +/** + * Class to handle building (compiling) extensions. + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since PHP 4.0.2 + * @see http://pear.php.net/manual/en/core.ppm.pear-builder.php + */ +class PEAR_Builder extends PEAR_Common +{ + var $php_api_version = 0; + var $zend_module_api_no = 0; + var $zend_extension_api_no = 0; + + var $extensions_built = array(); + + /** + * @var string Used for reporting when it is not possible to pass function + * via extra parameter, e.g. log, msdevCallback + */ + var $current_callback = null; + + // used for msdev builds + var $_lastline = null; + var $_firstline = null; + + /** + * PEAR_Builder constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Builder(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + } + + /** + * Build an extension from source on windows. + * requires msdev + */ + function _build_win32($descfile, $callback = null) + { + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + } else { + $pf = &new PEAR_PackageFile($this->config, $this->debug); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } + } + $dir = dirname($descfile); + $old_cwd = getcwd(); + + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { + return $this->raiseError("could not chdir to $dir"); + } + + // packages that were in a .tar have the packagefile in this directory + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); + if (file_exists($dir) && is_dir($vdir)) { + if (!chdir($vdir)) { + return $this->raiseError("could not chdir to " . realpath($vdir)); + } + + $dir = getcwd(); + } + + $this->log(2, "building in $dir"); + + $dsp = $pkg->getPackage().'.dsp'; + if (!file_exists("$dir/$dsp")) { + return $this->raiseError("The DSP $dsp does not exist."); + } + // XXX TODO: make release build type configurable + $command = 'msdev '.$dsp.' /MAKE "'.$pkg->getPackage(). ' - Release"'; + + $err = $this->_runCommand($command, array(&$this, 'msdevCallback')); + if (PEAR::isError($err)) { + return $err; + } + + // figure out the build platform and type + $platform = 'Win32'; + $buildtype = 'Release'; + if (preg_match('/.*?'.$pkg->getPackage().'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) { + $platform = $matches[1]; + $buildtype = $matches[2]; + } + + if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/', $this->_lastline, $matches)) { + if ($matches[2]) { + // there were errors in the build + return $this->raiseError("There were errors during compilation."); + } + $out = $matches[1]; + } else { + return $this->raiseError("Did not understand the completion status returned from msdev.exe."); + } + + // msdev doesn't tell us the output directory :/ + // open the dsp, find /out and use that directory + $dsptext = join(file($dsp),''); + + // this regex depends on the build platform and type having been + // correctly identified above. + $regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'. + $pkg->getPackage().'\s-\s'. + $platform.'\s'. + $buildtype.'").*?'. + '\/out:"(.*?)"/is'; + + if ($dsptext && preg_match($regex, $dsptext, $matches)) { + // what we get back is a relative path to the output file itself. + $outfile = realpath($matches[2]); + } else { + return $this->raiseError("Could not retrieve output information from $dsp."); + } + // realpath returns false if the file doesn't exist + if ($outfile && copy($outfile, "$dir/$out")) { + $outfile = "$dir/$out"; + } + + $built_files[] = array( + 'file' => "$outfile", + 'php_api' => $this->php_api_version, + 'zend_mod_api' => $this->zend_module_api_no, + 'zend_ext_api' => $this->zend_extension_api_no, + ); + + return $built_files; + } + // }}} + + // {{{ msdevCallback() + function msdevCallback($what, $data) + { + if (!$this->_firstline) + $this->_firstline = $data; + $this->_lastline = $data; + call_user_func($this->current_callback, $what, $data); + } + + /** + * @param string + * @param string + * @param array + * @access private + */ + function _harvestInstDir($dest_prefix, $dirname, &$built_files) + { + $d = opendir($dirname); + if (!$d) + return false; + + $ret = true; + while (($ent = readdir($d)) !== false) { + if ($ent{0} == '.') + continue; + + $full = $dirname . DIRECTORY_SEPARATOR . $ent; + if (is_dir($full)) { + if (!$this->_harvestInstDir( + $dest_prefix . DIRECTORY_SEPARATOR . $ent, + $full, $built_files)) { + $ret = false; + break; + } + } else { + $dest = $dest_prefix . DIRECTORY_SEPARATOR . $ent; + $built_files[] = array( + 'file' => $full, + 'dest' => $dest, + 'php_api' => $this->php_api_version, + 'zend_mod_api' => $this->zend_module_api_no, + 'zend_ext_api' => $this->zend_extension_api_no, + ); + } + } + closedir($d); + return $ret; + } + + /** + * Build an extension from source. Runs "phpize" in the source + * directory, but compiles in a temporary directory + * (TMPDIR/pear-build-USER/PACKAGE-VERSION). + * + * @param string|PEAR_PackageFile_v* $descfile path to XML package description file, or + * a PEAR_PackageFile object + * + * @param mixed $callback callback function used to report output, + * see PEAR_Builder::_runCommand for details + * + * @return array an array of associative arrays with built files, + * format: + * array( array( 'file' => '/path/to/ext.so', + * 'php_api' => YYYYMMDD, + * 'zend_mod_api' => YYYYMMDD, + * 'zend_ext_api' => YYYYMMDD ), + * ... ) + * + * @access public + * + * @see PEAR_Builder::_runCommand + */ + function build($descfile, $callback = null, $options = array()) + { + if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/', + $this->config->get('php_bin'), $matches)) { + if (isset($matches[2]) && strlen($matches[2]) && + trim($matches[2]) != trim($this->config->get('php_prefix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a prefix ' . $matches[2] . ', but' . + ' config variable php_prefix does not match'); + } + + if (isset($matches[3]) && strlen($matches[3]) && + trim($matches[3]) != trim($this->config->get('php_suffix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a suffix ' . $matches[3] . ', but' . + ' config variable php_suffix does not match'); + } + } + + $this->current_callback = $callback; + if (PEAR_OS == "Windows") { + return $this->_build_win32($descfile, $callback); + } + + if (PEAR_OS != 'Unix') { + return $this->raiseError("building extensions not supported on this platform"); + } + + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + if (is_a($pkg, 'PEAR_PackageFile_v1')) { + $dir = dirname($descfile); + } else { + $dir = $pkg->_config->get('temp_dir') . '/' . $pkg->getName(); + // automatically delete at session end + $this->addTempFile($dir); + } + } else { + $pf = &new PEAR_PackageFile($this->config); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } + $dir = dirname($descfile); + } + + // Find config. outside of normal path - e.g. config.m4 + foreach (array_keys($pkg->getInstallationFileList()) as $item) { + if (stristr(basename($item), 'config.m4') && dirname($item) != '.') { + $dir .= DIRECTORY_SEPARATOR . dirname($item); + break; + } + } + + $old_cwd = getcwd(); + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { + return $this->raiseError("could not chdir to $dir"); + } + + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); + if (is_dir($vdir)) { + chdir($vdir); + } + + $dir = getcwd(); + $this->log(2, "building in $dir"); + putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH')); + $err = $this->_runCommand($this->config->get('php_prefix') + . "phpize" . + $this->config->get('php_suffix'), + array(&$this, 'phpizeCallback')); + if (PEAR::isError($err)) { + return $err; + } + + if (!$err) { + return $this->raiseError("`phpize' failed"); + } + + // Figure out what params have been passed in to us already - formatting fixing + $opts = array(); + if (!empty($options)) { + foreach ($options as $op) { + $op = str_replace('--', '', $op); + list($name, $value) = explode('=', $op); + $opts[] = $name; + } + } + + // {{{ start of interactive part + $configure_command = "$dir/configure"; + $configure_options = $pkg->getConfigureOptions(); + if ($configure_options) { + foreach ($configure_options as $o) { + // skip params that have been passed already + if (in_array($o['name'], $opts)) { + continue; + } + + $default = array_key_exists('default', $o) ? $o['default'] : null; + list($r) = $this->ui->userDialog('build', + array($o['prompt']), + array('text'), + array($default)); + if (substr($o['name'], 0, 5) == 'with-' && + ($r == 'yes' || $r == 'autodetect')) { + $configure_command .= " --$o[name]"; + } else { + $configure_command .= " --$o[name]=".trim($r); + } + } + } + // }}} end of interactive part + + // Set any options that were passed in. + if (!empty($options)) { + foreach ($options as $op) { + $configure_command .= ' ' . $op; + } + } + + // FIXME make configurable + if (!$user = getenv('USER')){ + $user = 'defaultuser'; + } + + $tmpdir = $this->config->get('temp_dir'); + $build_basedir = System::mktemp(' -t "' . $tmpdir . '" -d "pear-build-' . $user . '"'); + $build_dir = "$build_basedir/$vdir"; + $inst_dir = "$build_basedir/install-$vdir"; + $this->log(1, "building in $build_dir"); + if (is_dir($build_dir)) { + System::rm(array('-rf', $build_dir)); + } + + if (!System::mkDir(array('-p', $build_dir))) { + return $this->raiseError("could not create build dir: $build_dir"); + } + + $this->addTempFile($build_dir); + if (!System::mkDir(array('-p', $inst_dir))) { + return $this->raiseError("could not create temporary install dir: $inst_dir"); + } + $this->addTempFile($inst_dir); + + $make_command = getenv('MAKE') ? getenv('MAKE') : 'make'; + + $to_run = array( + $configure_command, + $make_command, + "$make_command INSTALL_ROOT=\"$inst_dir\" install", + "find \"$inst_dir\" | xargs ls -dils" + ); + if (!file_exists($build_dir) || !is_dir($build_dir) || !chdir($build_dir)) { + return $this->raiseError("could not chdir to $build_dir"); + } + + putenv('PHP_PEAR_VERSION=1.9.4'); + foreach ($to_run as $cmd) { + $err = $this->_runCommand($cmd, $callback); + if (PEAR::isError($err)) { + chdir($old_cwd); + return $err; + } + if (!$err) { + chdir($old_cwd); + return $this->raiseError("`$cmd' failed"); + } + } + if (!($dp = opendir("modules"))) { + chdir($old_cwd); + return $this->raiseError("no `modules' directory found"); + } + + $built_files = array(); + $prefix = exec($this->config->get('php_prefix') + . "php-config" . + $this->config->get('php_suffix') . " --prefix"); + $ext_dir = $this->config->get('ext_dir'); + if (!$ext_dir) { + $ext_dir = $prefix; + } + $this->_harvestInstDir($ext_dir, $inst_dir . DIRECTORY_SEPARATOR . $prefix, $built_files); + + chdir($old_cwd); + return $built_files; + } + + /** + * Message callback function used when running the "phpize" + * program. Extracts the API numbers used. Ignores other message + * types than "cmdoutput". + * + * @param string $what the type of message + * @param mixed $data the message + * + * @return void + * + * @access public + */ + function phpizeCallback($what, $data) + { + if ($what != 'cmdoutput') { + return; + } + $this->log(1, rtrim($data)); + if (preg_match('/You should update your .aclocal.m4/', $data)) { + return; + } + $matches = array(); + if (preg_match('/^\s+(\S[^:]+):\s+(\d{8})/', $data, $matches)) { + $member = preg_replace('/[^a-z]/', '_', strtolower($matches[1])); + $apino = (int)$matches[2]; + if (isset($this->$member)) { + $this->$member = $apino; + //$msg = sprintf("%-22s : %d", $matches[1], $apino); + //$this->log(1, $msg); + } + } + } + + /** + * Run an external command, using a message callback to report + * output. The command will be run through popen and output is + * reported for every line with a "cmdoutput" message with the + * line string, including newlines, as payload. + * + * @param string $command the command to run + * + * @param mixed $callback (optional) function to use as message + * callback + * + * @return bool whether the command was successful (exit code 0 + * means success, any other means failure) + * + * @access private + */ + function _runCommand($command, $callback = null) + { + $this->log(1, "running: $command"); + $pp = popen("$command 2>&1", "r"); + if (!$pp) { + return $this->raiseError("failed to run `$command'"); + } + if ($callback && $callback[0]->debug == 1) { + $olddbg = $callback[0]->debug; + $callback[0]->debug = 2; + } + + while ($line = fgets($pp, 1024)) { + if ($callback) { + call_user_func($callback, 'cmdoutput', $line); + } else { + $this->log(2, rtrim($line)); + } + } + if ($callback && isset($olddbg)) { + $callback[0]->debug = $olddbg; + } + + $exitcode = is_resource($pp) ? pclose($pp) : -1; + return ($exitcode == 0); + } + + function log($level, $msg) + { + if ($this->current_callback) { + if ($this->debug >= $level) { + call_user_func($this->current_callback, 'output', $msg); + } + return; + } + return PEAR_Common::log($level, $msg); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/ChannelFile.php b/includes/pear/PEAR/ChannelFile.php new file mode 100644 index 0000000..9b27f28 --- /dev/null +++ b/includes/pear/PEAR/ChannelFile.php @@ -0,0 +1,1559 @@ +<?php +/** + * PEAR_ChannelFile, the channel handling class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR/ErrorStack.php'; +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/Common.php'; + +/** + * Error code if the channel.xml <channel> tag does not contain a valid version + */ +define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1); +/** + * Error code if the channel.xml <channel> tag version is not supported (version 1.0 is the only supported version, + * currently + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2); + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5); + +/**#@+ + * Validation errors + */ +/** + * Error code when channel name is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6); +/** + * Error code when channel name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7); +/** + * Error code when channel summary is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8); +/** + * Error code when channel summary is multi-line + */ +define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9); +/** + * Error code when channel server is missing for protocol + */ +define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10); +/** + * Error code when channel server is invalid for protocol + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11); +/** + * Error code when a mirror name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21); +/** + * Error code when a mirror type is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22); +/** + * Error code when an attempt is made to generate xml, but the parsed content is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID', 23); +/** + * Error code when an empty package name validate regex is passed in + */ +define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24); +/** + * Error code when a <function> tag has no version + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25); +/** + * Error code when a <function> tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26); +/** + * Error code when a <validatepackage> tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27); +/** + * Error code when a <validatepackage> tag has no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28); +/** + * Error code when a mirror does not exist but is called for in one of the set* + * methods. + */ +define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32); +/** + * Error code when a server port is not numeric + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33); +/** + * Error code when <static> contains no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34); +/** + * Error code when <baseurl> contains no type attribute in a <rest> protocol definition + */ +define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35); +/** + * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel + */ +define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36); +/** + * Error code when ssl attribute is present and is not "yes" + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37); +/**#@-*/ + +/** + * Mirror types allowed. Currently only internet servers are recognized. + */ +$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] = array('server'); + + +/** + * The Channel handling class + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * Supported channel.xml versions, for parsing + * @var array + * @access private + */ + var $_supportedVersions = array('1.0'); + + /** + * Parsed channel information + * @var array + * @access private + */ + var $_channelInfo; + + /** + * index into the subchannels array, used for parsing xml + * @var int + * @access private + */ + var $_subchannelIndex; + + /** + * index into the mirrors array, used for parsing xml + * @var int + * @access private + */ + var $_mirrorIndex; + + /** + * Flag used to determine the validity of parsed content + * @var boolean + * @access private + */ + var $_isValid = false; + + function PEAR_ChannelFile() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = false; + } + + /** + * @return array + * @access protected + */ + function _getErrorMessage() + { + return + array( + PEAR_CHANNELFILE_ERROR_INVALID_VERSION => + 'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%', + PEAR_CHANNELFILE_ERROR_NO_VERSION => + 'No version number found in <channel> tag', + PEAR_CHANNELFILE_ERROR_NO_XML_EXT => + '%error%', + PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER => + 'Unable to create XML parser', + PEAR_CHANNELFILE_ERROR_PARSER_ERROR => + '%error%', + PEAR_CHANNELFILE_ERROR_NO_NAME => + 'Missing channel name', + PEAR_CHANNELFILE_ERROR_INVALID_NAME => + 'Invalid channel %tag% "%name%"', + PEAR_CHANNELFILE_ERROR_NO_SUMMARY => + 'Missing channel summary', + PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY => + 'Channel summary should be on one line, but is multi-line', + PEAR_CHANNELFILE_ERROR_NO_HOST => + 'Missing channel server for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_HOST => + 'Server name "%server%" is invalid for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_MIRROR => + 'Invalid mirror name "%name%", mirror type %type%', + PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE => + 'Invalid mirror type "%type%"', + PEAR_CHANNELFILE_ERROR_INVALID => + 'Cannot generate xml, contents are invalid', + PEAR_CHANNELFILE_ERROR_EMPTY_REGEX => + 'packagenameregex cannot be empty', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION => + '%parent% %protocol% function has no version', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME => + '%parent% %protocol% function has no name', + PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE => + '%parent% rest baseurl has no type', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME => + 'Validation package has no name in <validatepackage> tag', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION => + 'Validation package "%package%" has no version', + PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND => + 'Mirror "%mirror%" does not exist', + PEAR_CHANNELFILE_ERROR_INVALID_PORT => + 'Port "%port%" must be numeric', + PEAR_CHANNELFILE_ERROR_NO_STATICVERSION => + '<static> tag must contain version attribute', + PEAR_CHANNELFILE_URI_CANT_MIRROR => + 'The __uri pseudo-channel cannot have mirrors', + PEAR_CHANNELFILE_ERROR_INVALID_SSL => + '%server% has invalid ssl attribute "%ssl%" can only be yes or not present', + ); + } + + /** + * @param string contents of package.xml file + * @return bool success of parsing + */ + function fromXmlString($data) + { + if (preg_match('/<channel\s+version="([0-9]+\.[0-9]+)"/', $data, $channelversion)) { + if (!in_array($channelversion[1], $this->_supportedVersions)) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error', + array('version' => $channelversion[1])); + return false; + } + $parser = new PEAR_XMLParser; + $result = $parser->parse($data); + if ($result !== true) { + if ($result->getCode() == 1) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error', + array('error' => $result->getMessage())); + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error'); + } + return false; + } + $this->_channelInfo = $parser->getData(); + return true; + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data)); + return false; + } + } + + /** + * @return array + */ + function toArray() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + return $this->_channelInfo; + } + + /** + * @param array + * @static + * @return PEAR_ChannelFile|false false if invalid + */ + function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + if (!$a->validate()) { + $a = false; + return $a; + } + return $a; + } + + /** + * Unlike {@link fromArray()} this does not do any validation + * @param array + * @static + * @return PEAR_ChannelFile + */ + function &fromArrayWithErrors($data, $compatibility = false, + $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + return $a; + } + + /** + * @param array + * @access private + */ + function _fromArray($data) + { + $this->_channelInfo = $data; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getErrors($purge = false) + { + return $this->_stack->getErrors($purge); + } + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * Parse a channel.xml file. Expects the name of + * a channel xml file as input. + * + * @param string $descfile name of channel xml file + * @return bool success of parsing + */ + function fromXmlFile($descfile) + { + if (!file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) || + (!$fp = fopen($descfile, 'r'))) { + require_once 'PEAR.php'; + return PEAR::raiseError("Unable to open $descfile"); + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + return $this->fromXmlString($data); + } + + /** + * Parse channel information from different sources + * + * This method is able to extract information about a channel + * from an .xml file or a string + * + * @access public + * @param string Filename of the source or the source itself + * @return bool + */ + function fromAny($info) + { + if (is_string($info) && file_exists($info) && strlen($info) < 255) { + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = $this->fromXmlFile($info); + } else { + $fp = fopen($info, "r"); + $test = fread($fp, 5); + fclose($fp); + if ($test == "<?xml") { + $info = $this->fromXmlFile($info); + } + } + if (PEAR::isError($info)) { + require_once 'PEAR.php'; + return PEAR::raiseError($info); + } + } + if (is_string($info)) { + $info = $this->fromXmlString($info); + } + return $info; + } + + /** + * Return an XML document based on previous parsing and modifications + * + * @return string XML data + * + * @access public + */ + function toXml() + { + if (!$this->_isValid && !$this->validate()) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID); + return false; + } + if (!isset($this->_channelInfo['attribs']['version'])) { + $this->_channelInfo['attribs']['version'] = '1.0'; + } + $channelInfo = $this->_channelInfo; + $ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n"; + $ret .= "<channel version=\"" . + $channelInfo['attribs']['version'] . "\" xmlns=\"http://pear.php.net/channel-1.0\" + xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" + xsi:schemaLocation=\"http://pear.php.net/dtd/channel-" + . $channelInfo['attribs']['version'] . " http://pear.php.net/dtd/channel-" . + $channelInfo['attribs']['version'] . ".xsd\"> + <name>$channelInfo[name]</name> + <summary>" . htmlspecialchars($channelInfo['summary'])."</summary> +"; + if (isset($channelInfo['suggestedalias'])) { + $ret .= ' <suggestedalias>' . $channelInfo['suggestedalias'] . "</suggestedalias>\n"; + } + if (isset($channelInfo['validatepackage'])) { + $ret .= ' <validatepackage version="' . + $channelInfo['validatepackage']['attribs']['version']. '">' . + htmlspecialchars($channelInfo['validatepackage']['_content']) . + "</validatepackage>\n"; + } + $ret .= " <servers>\n"; + $ret .= ' <primary'; + if (isset($channelInfo['servers']['primary']['attribs']['ssl'])) { + $ret .= ' ssl="' . $channelInfo['servers']['primary']['attribs']['ssl'] . '"'; + } + if (isset($channelInfo['servers']['primary']['attribs']['port'])) { + $ret .= ' port="' . $channelInfo['servers']['primary']['attribs']['port'] . '"'; + } + $ret .= ">\n"; + if (isset($channelInfo['servers']['primary']['rest'])) { + $ret .= $this->_makeRestXml($channelInfo['servers']['primary']['rest'], ' '); + } + $ret .= " </primary>\n"; + if (isset($channelInfo['servers']['mirror'])) { + $ret .= $this->_makeMirrorsXml($channelInfo); + } + $ret .= " </servers>\n"; + $ret .= "</channel>"; + return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret)); + } + + /** + * Generate the <rest> tag + * @access private + */ + function _makeRestXml($info, $indent) + { + $ret = $indent . "<rest>\n"; + if (isset($info['baseurl']) && !isset($info['baseurl'][0])) { + $info['baseurl'] = array($info['baseurl']); + } + + if (isset($info['baseurl'])) { + foreach ($info['baseurl'] as $url) { + $ret .= "$indent <baseurl type=\"" . $url['attribs']['type'] . "\""; + $ret .= ">" . $url['_content'] . "</baseurl>\n"; + } + } + $ret .= $indent . "</rest>\n"; + return $ret; + } + + /** + * Generate the <mirrors> tag + * @access private + */ + function _makeMirrorsXml($channelInfo) + { + $ret = ""; + if (!isset($channelInfo['servers']['mirror'][0])) { + $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']); + } + foreach ($channelInfo['servers']['mirror'] as $mirror) { + $ret .= ' <mirror host="' . $mirror['attribs']['host'] . '"'; + if (isset($mirror['attribs']['port'])) { + $ret .= ' port="' . $mirror['attribs']['port'] . '"'; + } + if (isset($mirror['attribs']['ssl'])) { + $ret .= ' ssl="' . $mirror['attribs']['ssl'] . '"'; + } + $ret .= ">\n"; + if (isset($mirror['rest'])) { + if (isset($mirror['rest'])) { + $ret .= $this->_makeRestXml($mirror['rest'], ' '); + } + $ret .= " </mirror>\n"; + } else { + $ret .= "/>\n"; + } + } + return $ret; + } + + /** + * Generate the <functions> tag + * @access private + */ + function _makeFunctionsXml($functions, $indent, $rest = false) + { + $ret = ''; + if (!isset($functions[0])) { + $functions = array($functions); + } + foreach ($functions as $function) { + $ret .= "$indent<function version=\"" . $function['attribs']['version'] . "\""; + if ($rest) { + $ret .= ' uri="' . $function['attribs']['uri'] . '"'; + } + $ret .= ">" . $function['_content'] . "</function>\n"; + } + return $ret; + } + + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params); + } + + /** + * Validate parsed file. + * + * @access public + * @return boolean + */ + function validate() + { + $this->_isValid = true; + $info = $this->_channelInfo; + if (empty($info['name'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME); + } elseif (!$this->validChannelServer($info['name'])) { + if ($info['name'] != '__uri') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name', + 'name' => $info['name'])); + } + } + if (empty($info['summary'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (isset($info['suggestedalias'])) { + if (!$this->validChannelServer($info['suggestedalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias'])); + } + } + if (isset($info['localalias'])) { + if (!$this->validChannelServer($info['localalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'localalias', 'name' =>$info['localalias'])); + } + } + if (isset($info['validatepackage'])) { + if (!isset($info['validatepackage']['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME); + } + if (!isset($info['validatepackage']['attribs']['version'])) { + $content = isset($info['validatepackage']['_content']) ? + $info['validatepackage']['_content'] : + null; + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION, + array('package' => $content)); + } + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['port']) && + !is_numeric($info['servers']['primary']['attribs']['port'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT, + array('port' => $info['servers']['primary']['attribs']['port'])); + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['ssl']) && + $info['servers']['primary']['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['servers']['primary']['attribs']['ssl'], + 'server' => $info['name'])); + } + + if (isset($info['servers']['primary']['rest']) && + isset($info['servers']['primary']['rest']['baseurl'])) { + $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']); + } + if (isset($info['servers']['mirror'])) { + if ($this->_channelInfo['name'] == '__uri') { + $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR); + } + if (!isset($info['servers']['mirror'][0])) { + $info['servers']['mirror'] = array($info['servers']['mirror']); + } + foreach ($info['servers']['mirror'] as $mirror) { + if (!isset($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST, + array('type' => 'mirror')); + } elseif (!$this->validChannelServer($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST, + array('server' => $mirror['attribs']['host'], 'type' => 'mirror')); + } + if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host'])); + } + if (isset($mirror['rest'])) { + $this->_validateFunctions('rest', $mirror['rest']['baseurl'], + $mirror['attribs']['host']); + } + } + } + return $this->_isValid; + } + + /** + * @param string rest - protocol name this function applies to + * @param array the functions + * @param string the name of the parent element (mirror name, for instance) + */ + function _validateFunctions($protocol, $functions, $parent = '') + { + if (!isset($functions[0])) { + $functions = array($functions); + } + + foreach ($functions as $function) { + if (!isset($function['_content']) || empty($function['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME, + array('parent' => $parent, 'protocol' => $protocol)); + } + + if ($protocol == 'rest') { + if (!isset($function['attribs']['type']) || + empty($function['attribs']['type'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE, + array('parent' => $parent, 'protocol' => $protocol)); + } + } else { + if (!isset($function['attribs']['version']) || + empty($function['attribs']['version'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION, + array('parent' => $parent, 'protocol' => $protocol)); + } + } + } + } + + /** + * Test whether a string contains a valid channel server. + * @param string $ver the package version to test + * @return bool + */ + function validChannelServer($server) + { + if ($server == '__uri') { + return true; + } + return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server); + } + + /** + * @return string|false + */ + function getName() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return string|false + */ + function getServer() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return int|80 port number to connect to + */ + function getPort($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['port'])) { + return $mir['attribs']['port']; + } + + if ($this->getSSL($mirror)) { + return 443; + } + + return 80; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) { + return $this->_channelInfo['servers']['primary']['attribs']['port']; + } + + if ($this->getSSL()) { + return 443; + } + + return 80; + } + + /** + * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel + */ + function getSSL($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['ssl'])) { + return true; + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + return true; + } + + return false; + } + + /** + * @return string|false + */ + function getSummary() + { + if (isset($this->_channelInfo['summary'])) { + return $this->_channelInfo['summary']; + } + + return false; + } + + /** + * @param string protocol type + * @param string Mirror name + * @return array|false + */ + function getFunctions($protocol, $mirror = false) + { + if ($this->getName() == '__uri') { + return false; + } + + $function = $protocol == 'rest' ? 'baseurl' : 'function'; + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir[$protocol][$function])) { + return $mir[$protocol][$function]; + } + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) { + return $this->_channelInfo['servers']['primary'][$protocol][$function]; + } + + return false; + } + + /** + * @param string Protocol type + * @param string Function name (null to return the + * first protocol of the type requested) + * @param string Mirror name, if any + * @return array + */ + function getFunction($type, $name = null, $mirror = false) + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($name === null) { + return $protocol; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return $protocol; + } + + return false; + } + + /** + * @param string protocol type + * @param string protocol name + * @param string version + * @param string mirror name + * @return boolean + */ + function supports($type, $name = null, $mirror = false, $version = '1.0') + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($protocol['attribs']['version'] != $version) { + continue; + } + + if ($name === null) { + return true; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return true; + } + + return false; + } + + /** + * Determines whether a channel supports Representational State Transfer (REST) protocols + * for retrieving channel information + * @param string + * @return bool + */ + function supportsREST($mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + return isset($mir['rest']); + } + + return false; + } + + return isset($this->_channelInfo['servers']['primary']['rest']); + } + + /** + * Get the URL to access a base resource. + * + * Hyperlinks in the returned xml will be used to retrieve the proper information + * needed. This allows extreme extensibility and flexibility in implementation + * @param string Resource Type to retrieve + */ + function getBaseURL($resourceType, $mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + $mir = $this->getMirror($mirror); + if (!$mir) { + return false; + } + + $rest = $mir['rest']; + } else { + $rest = $this->_channelInfo['servers']['primary']['rest']; + } + + if (!isset($rest['baseurl'][0])) { + $rest['baseurl'] = array($rest['baseurl']); + } + + foreach ($rest['baseurl'] as $baseurl) { + if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) { + return $baseurl['_content']; + } + } + + return false; + } + + /** + * Since REST does not implement RPC, provide this as a logical wrapper around + * resetFunctions for REST + * @param string|false mirror name, if any + */ + function resetREST($mirror = false) + { + return $this->resetFunctions('rest', $mirror); + } + + /** + * Empty all protocol definitions + * @param string protocol type + * @param string|false mirror name, if any + */ + function resetFunctions($type, $mirror = false) + { + if ($mirror) { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + foreach ($mirrors as $i => $mir) { + if ($mir['attribs']['host'] == $mirror) { + if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) { + unset($this->_channelInfo['servers']['mirror'][$i][$type]); + } + + return true; + } + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$type])) { + unset($this->_channelInfo['servers']['primary'][$type]); + } + + return true; + } + + /** + * Set a channel's protocols to the protocols supported by pearweb + */ + function setDefaultPEARProtocols($version = '1.0', $mirror = false) + { + switch ($version) { + case '1.0' : + $this->resetREST($mirror); + + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array('rest' => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array('rest' => array()); + } + + return true; + break; + default : + return false; + break; + } + } + + /** + * @return array + */ + function getMirrors() + { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + return $mirrors; + } + + return array(); + } + + /** + * Get the unserialized XML representing a mirror + * @return array|false + */ + function getMirror($server) + { + foreach ($this->getMirrors() as $mirror) { + if ($mirror['attribs']['host'] == $server) { + return $mirror; + } + } + + return false; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_NAME + * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME + */ + function setName($name) + { + return $this->setServer($name); + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param integer + * @param string|false name of the mirror server, or false for the primary + */ + function setPort($port, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port; + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + } + + $this->_channelInfo['servers']['primary']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param bool Determines whether to turn on SSL support or turn it off + * @param string|false name of the mirror server, or false for the primary + */ + function setSSL($ssl = true, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror'][$i] + ['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes'; + } + + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes'; + } + + $this->_isValid = false; + return true; + } + } + + if ($ssl) { + $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes'; + } else { + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['primary']['attribs']['ssl']); + } + } + + $this->_isValid = false; + return true; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_SERVER + * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER + */ + function setServer($server, $mirror = false) + { + if (empty($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER); + return false; + } elseif (!$this->validChannelServer($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'name', 'name' => $server)); + return false; + } + + if ($mirror) { + $found = false; + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $found = true; + break; + } + } + + if (!$found) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server; + return true; + } + + $this->_channelInfo['name'] = $server; + return true; + } + + /** + * @param string + * @return boolean success + * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY + * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY + */ + function setSummary($summary) + { + if (empty($summary)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + return false; + } elseif (strpos(trim($summary), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $summary)); + } + + $this->_channelInfo['summary'] = $summary; + return true; + } + + /** + * @param string + * @param boolean determines whether the alias is in channel.xml or local + * @return boolean success + */ + function setAlias($alias, $local = false) + { + if (!$this->validChannelServer($alias)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' => $alias)); + return false; + } + + if ($local) { + $this->_channelInfo['localalias'] = $alias; + } else { + $this->_channelInfo['suggestedalias'] = $alias; + } + + return true; + } + + /** + * @return string + */ + function getAlias() + { + if (isset($this->_channelInfo['localalias'])) { + return $this->_channelInfo['localalias']; + } + if (isset($this->_channelInfo['suggestedalias'])) { + return $this->_channelInfo['suggestedalias']; + } + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + return ''; + } + + /** + * Set the package validation object if it differs from PEAR's default + * The class must be includeable via changing _ in the classname to path separator, + * but no checking of this is made. + * @param string|false pass in false to reset to the default packagename regex + * @return boolean success + */ + function setValidationPackage($validateclass, $version) + { + if (empty($validateclass)) { + unset($this->_channelInfo['validatepackage']); + } + $this->_channelInfo['validatepackage'] = array('_content' => $validateclass); + $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version); + } + + /** + * Add a protocol to the provides section + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + * @param string mirror name, if this is a mirror's protocol + * @return bool + */ + function addFunction($type, $version, $name = '', $mirror = false) + { + if ($mirror) { + return $this->addMirrorFunction($mirror, $type, $version, $name); + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) { + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array($type => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array($type => array()); + } + + $this->_channelInfo['servers']['primary'][$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) { + $this->_channelInfo['servers']['primary'][$type]['function'] = array( + $this->_channelInfo['servers']['primary'][$type]['function']); + } + + $this->_channelInfo['servers']['primary'][$type]['function'][] = $set; + return true; + } + /** + * Add a protocol to a mirror's provides section + * @param string mirror name (server) + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + */ + function addMirrorFunction($mirror, $type, $version, $name = '') + { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + + if (!$setmirror) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($setmirror[$type]['function'])) { + $setmirror[$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror[$type]['function'][0])) { + $setmirror[$type]['function'] = array($setmirror[$type]['function']); + } + + $setmirror[$type]['function'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string Resource Type this url links to + * @param string URL + * @param string|false mirror name, if this is not a primary server REST base URL + */ + function setBaseURL($resourceType, $url, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + } else { + $setmirror = &$this->_channelInfo['servers']['primary']; + } + + $set = array('attribs' => array('type' => $resourceType), '_content' => $url); + if (!isset($setmirror['rest'])) { + $setmirror['rest'] = array(); + } + + if (!isset($setmirror['rest']['baseurl'])) { + $setmirror['rest']['baseurl'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror['rest']['baseurl'][0])) { + $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']); + } + + foreach ($setmirror['rest']['baseurl'] as $i => $url) { + if ($url['attribs']['type'] == $resourceType) { + $this->_isValid = false; + $setmirror['rest']['baseurl'][$i] = $set; + return true; + } + } + + $setmirror['rest']['baseurl'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string mirror server + * @param int mirror http port + * @return boolean + */ + function addMirror($server, $port = null) + { + if ($this->_channelInfo['name'] == '__uri') { + return false; // the __uri channel cannot have mirrors by definition + } + + $set = array('attribs' => array('host' => $server)); + if (is_numeric($port)) { + $set['attribs']['port'] = $port; + } + + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_channelInfo['servers']['mirror'] = $set; + return true; + } + + if (!isset($this->_channelInfo['servers']['mirror'][0])) { + $this->_channelInfo['servers']['mirror'] = + array($this->_channelInfo['servers']['mirror']); + } + + $this->_channelInfo['servers']['mirror'][] = $set; + return true; + } + + /** + * Retrieve the name of the validation package for this channel + * @return string|false + */ + function getValidationPackage() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + + if (!isset($this->_channelInfo['validatepackage'])) { + return array('attribs' => array('version' => 'default'), + '_content' => 'PEAR_Validate'); + } + + return $this->_channelInfo['validatepackage']; + } + + /** + * Retrieve the object that can be used for custom validation + * @param string|false the name of the package to validate. If the package is + * the channel validation package, PEAR_Validate is returned + * @return PEAR_Validate|false false is returned if the validation package + * cannot be located + */ + function &getValidationObject($package = false) + { + if (!class_exists('PEAR_Validate')) { + require_once 'PEAR/Validate.php'; + } + + if (!$this->_isValid) { + if (!$this->validate()) { + $a = false; + return $a; + } + } + + if (isset($this->_channelInfo['validatepackage'])) { + if ($package == $this->_channelInfo['validatepackage']) { + // channel validation packages are always validated by PEAR_Validate + $val = &new PEAR_Validate; + return $val; + } + + if (!class_exists(str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']))) { + if ($this->isIncludeable(str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php')) { + include_once str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php'; + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } else { + $a = false; + return $a; + } + } else { + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } + } else { + $val = &new PEAR_Validate; + } + + return $val; + } + + function isIncludeable($path) + { + $possibilities = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($possibilities as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $path) + && is_readable($dir . DIRECTORY_SEPARATOR . $path)) { + return true; + } + } + + return false; + } + + /** + * This function is used by the channel updater and retrieves a value set by + * the registry, or the current time if it has not been set + * @return string + */ + function lastModified() + { + if (isset($this->_channelInfo['_lastmodified'])) { + return $this->_channelInfo['_lastmodified']; + } + + return time(); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/ChannelFile/Parser.php b/includes/pear/PEAR/ChannelFile/Parser.php new file mode 100644 index 0000000..5416c3d --- /dev/null +++ b/includes/pear/PEAR/ChannelFile/Parser.php @@ -0,0 +1,68 @@ +<?php +/** + * PEAR_ChannelFile_Parser for parsing channel.xml + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/ChannelFile.php'; +/** + * Parser for channel.xml + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile_Parser extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + function parse($data, $file) + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new PEAR_ChannelFile; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + // make sure the filelist is in the easy to read format needed + $ret->flattenFilelist(); + $ret->setPackagefile($file, $archive); + return $ret; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command.php b/includes/pear/PEAR/Command.php new file mode 100644 index 0000000..5dd3cc5 --- /dev/null +++ b/includes/pear/PEAR/Command.php @@ -0,0 +1,414 @@ +<?php +/** + * PEAR_Command, command pattern class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Frontend.php'; +require_once 'PEAR/XMLParser.php'; + +/** + * List of commands and what classes they are implemented in. + * @var array command => implementing class + */ +$GLOBALS['_PEAR_Command_commandlist'] = array(); + +/** + * List of commands and their descriptions + * @var array command => description + */ +$GLOBALS['_PEAR_Command_commanddesc'] = array(); + +/** + * List of shortcuts to common commands. + * @var array shortcut => command + */ +$GLOBALS['_PEAR_Command_shortcuts'] = array(); + +/** + * Array of command objects + * @var array class => object + */ +$GLOBALS['_PEAR_Command_objects'] = array(); + +/** + * PEAR command class, a simple factory class for administrative + * commands. + * + * How to implement command classes: + * + * - The class must be called PEAR_Command_Nnn, installed in the + * "PEAR/Common" subdir, with a method called getCommands() that + * returns an array of the commands implemented by the class (see + * PEAR/Command/Install.php for an example). + * + * - The class must implement a run() function that is called with three + * params: + * + * (string) command name + * (array) assoc array with options, freely defined by each + * command, for example: + * array('force' => true) + * (array) list of the other parameters + * + * The run() function returns a PEAR_CommandResponse object. Use + * these methods to get information: + * + * int getStatus() Returns PEAR_COMMAND_(SUCCESS|FAILURE|PARTIAL) + * *_PARTIAL means that you need to issue at least + * one more command to complete the operation + * (used for example for validation steps). + * + * string getMessage() Returns a message for the user. Remember, + * no HTML or other interface-specific markup. + * + * If something unexpected happens, run() returns a PEAR error. + * + * - DON'T OUTPUT ANYTHING! Return text for output instead. + * + * - DON'T USE HTML! The text you return will be used from both Gtk, + * web and command-line interfaces, so for now, keep everything to + * plain text. + * + * - DON'T USE EXIT OR DIE! Always use pear errors. From static + * classes do PEAR::raiseError(), from other classes do + * $this->raiseError(). + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command +{ + // {{{ factory() + + /** + * Get the right object for executing a command. + * + * @param string $command The name of the command + * @param object $config Instance of PEAR_Config object + * + * @return object the command object or a PEAR error + * + * @access public + * @static + */ + function &factory($command, &$config) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $ui =& PEAR_Command::getFrontendObject(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getObject() + function &getObject($command) + { + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + return PEAR::raiseError("unknown command `$command'"); + } + $ui =& PEAR_Command::getFrontendObject(); + $config = &PEAR_Config::singleton(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getFrontendObject() + + /** + * Get instance of frontend object. + * + * @return object|PEAR_Error + * @static + */ + function &getFrontendObject() + { + $a = &PEAR_Frontend::singleton(); + return $a; + } + + // }}} + // {{{ & setFrontendClass() + + /** + * Load current frontend class. + * + * @param string $uiclass Name of class implementing the frontend + * + * @return object the frontend object, or a PEAR error + * @static + */ + function &setFrontendClass($uiclass) + { + $a = &PEAR_Frontend::setFrontendClass($uiclass); + return $a; + } + + // }}} + // {{{ setFrontendType() + + /** + * Set current frontend. + * + * @param string $uitype Name of the frontend type (for example "CLI") + * + * @return object the frontend object, or a PEAR error + * @static + */ + function setFrontendType($uitype) + { + $uiclass = 'PEAR_Frontend_' . $uitype; + return PEAR_Command::setFrontendClass($uiclass); + } + + // }}} + // {{{ registerCommands() + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * + * @param bool (optional) if FALSE (default), the new list of + * commands should replace the current one. If TRUE, + * new entries will be merged with old. + * + * @param string (optional) where (what directory) to look for + * classes, defaults to the Command subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * + * @access public + * @static + */ + function registerCommands($merge = false, $dir = null) + { + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Command'; + } + if (!is_dir($dir)) { + return PEAR::raiseError("registerCommands: opendir($dir) '$dir' does not exist or is not a directory"); + } + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerCommands: opendir($dir) failed"); + } + if (!$merge) { + $GLOBALS['_PEAR_Command_commandlist'] = array(); + } + + while ($file = readdir($dp)) { + if ($file{0} == '.' || substr($file, -4) != '.xml') { + continue; + } + + $f = substr($file, 0, -4); + $class = "PEAR_Command_" . $f; + // List of commands + if (empty($GLOBALS['_PEAR_Command_objects'][$class])) { + $GLOBALS['_PEAR_Command_objects'][$class] = "$dir/" . $f . '.php'; + } + + $parser->parse(file_get_contents("$dir/$file")); + $implements = $parser->getData(); + foreach ($implements as $command => $desc) { + if ($command == 'attribs') { + continue; + } + + if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return PEAR::raiseError('Command "' . $command . '" already registered in ' . + 'class "' . $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + + $GLOBALS['_PEAR_Command_commandlist'][$command] = $class; + $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc['summary']; + if (isset($desc['shortcut'])) { + $shortcut = $desc['shortcut']; + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$shortcut])) { + return PEAR::raiseError('Command shortcut "' . $shortcut . '" already ' . + 'registered to command "' . $command . '" in class "' . + $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command; + } + + if (isset($desc['options']) && $desc['options']) { + foreach ($desc['options'] as $oname => $option) { + if (isset($option['shortopt']) && strlen($option['shortopt']) > 1) { + return PEAR::raiseError('Option "' . $oname . '" short option "' . + $option['shortopt'] . '" must be ' . + 'only 1 character in Command "' . $command . '" in class "' . + $class . '"'); + } + } + } + } + } + + ksort($GLOBALS['_PEAR_Command_shortcuts']); + ksort($GLOBALS['_PEAR_Command_commandlist']); + @closedir($dp); + return true; + } + + // }}} + // {{{ getCommands() + + /** + * Get the list of currently supported commands, and what + * classes implement them. + * + * @return array command => implementing class + * + * @access public + * @static + */ + function getCommands() + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_commandlist']; + } + + // }}} + // {{{ getShortcuts() + + /** + * Get the list of command shortcuts. + * + * @return array shortcut => command + * + * @access public + * @static + */ + function getShortcuts() + { + if (empty($GLOBALS['_PEAR_Command_shortcuts'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_shortcuts']; + } + + // }}} + // {{{ getGetoptArgs() + + /** + * Compiles arguments for getopt. + * + * @param string $command command to get optstring for + * @param string $short_args (reference) short getopt format + * @param array $long_args (reference) long getopt format + * + * @return void + * + * @access public + * @static + */ + function getGetoptArgs($command, &$short_args, &$long_args) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return null; + } + $obj = &PEAR_Command::getObject($command); + return $obj->getGetoptArgs($command, $short_args, $long_args); + } + + // }}} + // {{{ getDescription() + + /** + * Get description for a command. + * + * @param string $command Name of the command + * + * @return string command description + * + * @access public + * @static + */ + function getDescription($command) + { + if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) { + return null; + } + return $GLOBALS['_PEAR_Command_commanddesc'][$command]; + } + + // }}} + // {{{ getHelp() + + /** + * Get help for command. + * + * @param string $command Name of the command to return help for + * + * @access public + * @static + */ + function getHelp($command) + { + $cmds = PEAR_Command::getCommands(); + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (isset($cmds[$command])) { + $obj = &PEAR_Command::getObject($command); + return $obj->getHelp($command); + } + return false; + } + // }}} +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Auth.php b/includes/pear/PEAR/Command/Auth.php new file mode 100644 index 0000000..5376bc3 --- /dev/null +++ b/includes/pear/PEAR/Command/Auth.php @@ -0,0 +1,81 @@ +<?php +/** + * PEAR_Command_Auth (login, logout commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Channels.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ +class PEAR_Command_Auth extends PEAR_Command_Channels +{ + var $commands = array( + 'login' => array( + 'summary' => 'Connects and authenticates to remote server [Deprecated in favor of channel-login]', + 'shortcut' => 'li', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => '<channel name> +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'logout' => array( + 'summary' => 'Logs out from the remote server [Deprecated in favor of channel-logout]', + 'shortcut' => 'lo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +WARNING: This function is deprecated in favor of using channel-logout + +Logs out from the remote server. This command does not actually +connect to the remote server, it only deletes the stored username and +password from your user configuration.', + ) + + ); + + /** + * PEAR_Command_Auth constructor. + * + * @access public + */ + function PEAR_Command_Auth(&$ui, &$config) + { + parent::PEAR_Command_Channels($ui, $config); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Auth.xml b/includes/pear/PEAR/Command/Auth.xml new file mode 100644 index 0000000..590193d --- /dev/null +++ b/includes/pear/PEAR/Command/Auth.xml @@ -0,0 +1,30 @@ +<commands version="1.0"> + <login> + <summary>Connects and authenticates to remote server [Deprecated in favor of channel-login]</summary> + <function>doLogin</function> + <shortcut>li</shortcut> + <options /> + <doc><channel name> +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.</doc> + </login> + <logout> + <summary>Logs out from the remote server [Deprecated in favor of channel-logout]</summary> + <function>doLogout</function> + <shortcut>lo</shortcut> + <options /> + <doc> +WARNING: This function is deprecated in favor of using channel-logout + +Logs out from the remote server. This command does not actually +connect to the remote server, it only deletes the stored username and +password from your user configuration.</doc> + </logout> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Build.php b/includes/pear/PEAR/Command/Build.php new file mode 100644 index 0000000..67a61d3 --- /dev/null +++ b/includes/pear/PEAR/Command/Build.php @@ -0,0 +1,85 @@ +<?php +/** + * PEAR_Command_Auth (build command) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for building extensions. + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Build extends PEAR_Command_Common +{ + var $commands = array( + 'build' => array( + 'summary' => 'Build an Extension From C Source', + 'function' => 'doBuild', + 'shortcut' => 'b', + 'options' => array(), + 'doc' => '[package.xml] +Builds one or more extensions contained in a package.' + ), + ); + + /** + * PEAR_Command_Build constructor. + * + * @access public + */ + function PEAR_Command_Build(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doBuild($command, $options, $params) + { + require_once 'PEAR/Builder.php'; + if (sizeof($params) < 1) { + $params[0] = 'package.xml'; + } + + $builder = &new PEAR_Builder($this->ui); + $this->debug = $this->config->get('verbose'); + $err = $builder->build($params[0], array(&$this, 'buildCallback')); + if (PEAR::isError($err)) { + return $err; + } + + return true; + } + + function buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Build.xml b/includes/pear/PEAR/Command/Build.xml new file mode 100644 index 0000000..ec4e6f5 --- /dev/null +++ b/includes/pear/PEAR/Command/Build.xml @@ -0,0 +1,10 @@ +<commands version="1.0"> + <build> + <summary>Build an Extension From C Source</summary> + <function>doBuild</function> + <shortcut>b</shortcut> + <options /> + <doc>[package.xml] +Builds one or more extensions contained in a package.</doc> + </build> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Channels.php b/includes/pear/PEAR/Command/Channels.php new file mode 100644 index 0000000..5ab8f42 --- /dev/null +++ b/includes/pear/PEAR/Command/Channels.php @@ -0,0 +1,883 @@ +<?php +// /* vim: set expandtab tabstop=4 shiftwidth=4: */ +/** + * PEAR_Command_Channels (list-channels, update-channels, channel-delete, channel-add, + * channel-update, channel-info, channel-alias, channel-discover commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +define('PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS', -500); + +/** + * PEAR commands for managing channels. + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Command_Channels extends PEAR_Command_Common +{ + var $commands = array( + 'list-channels' => array( + 'summary' => 'List Available Channels', + 'function' => 'doList', + 'shortcut' => 'lc', + 'options' => array(), + 'doc' => ' +List all available channels for installation. +', + ), + 'update-channels' => array( + 'summary' => 'Update the Channel List', + 'function' => 'doUpdateAll', + 'shortcut' => 'uc', + 'options' => array(), + 'doc' => ' +List all installed packages in all channels. +' + ), + 'channel-delete' => array( + 'summary' => 'Remove a Channel From the List', + 'function' => 'doDelete', + 'shortcut' => 'cde', + 'options' => array(), + 'doc' => '<channel name> +Delete a channel from the registry. You may not +remove any channel that has installed packages. +' + ), + 'channel-add' => array( + 'summary' => 'Add a Channel', + 'function' => 'doAdd', + 'shortcut' => 'ca', + 'options' => array(), + 'doc' => '<channel.xml> +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. +' + ), + 'channel-update' => array( + 'summary' => 'Update an Existing Channel', + 'function' => 'doUpdate', + 'shortcut' => 'cu', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), + 'channel' => array( + 'shortopt' => 'c', + 'arg' => 'CHANNEL', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), +), + 'doc' => '[<channel.xml>|<channel name>] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. +' + ), + 'channel-info' => array( + 'summary' => 'Retrieve Information on a Channel', + 'function' => 'doInfo', + 'shortcut' => 'ci', + 'options' => array(), + 'doc' => '<package> +List the files in an installed package. +' + ), + 'channel-alias' => array( + 'summary' => 'Specify an alias to a channel name', + 'function' => 'doAlias', + 'shortcut' => 'cha', + 'options' => array(), + 'doc' => '<channel> <alias> +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. +' + ), + 'channel-discover' => array( + 'summary' => 'Initialize a Channel from its server', + 'function' => 'doDiscover', + 'shortcut' => 'di', + 'options' => array(), + 'doc' => '[<channel.xml>|<channel name>] +Initialize a channel from its server and create a local channel.xml. +If <channel name> is in the format "<username>:<password>@<channel>" then +<username> and <password> will be set as the login username/password for +<channel>. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system\'s process list. +' + ), + 'channel-login' => array( + 'summary' => 'Connects and authenticates to remote channel server', + 'shortcut' => 'cli', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => '<channel name> +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'channel-logout' => array( + 'summary' => 'Logs out from the remote channel server', + 'shortcut' => 'clo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => '<channel name> +Logs out from a remote channel server. If <channel name> is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration.', + ), + ); + + /** + * PEAR_Command_Registry constructor. + * + * @access public + */ + function PEAR_Command_Channels(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _sortChannels($a, $b) + { + return strnatcasecmp($a->getName(), $b->getName()); + } + + function doList($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $registered = $reg->getChannels(); + usort($registered, array(&$this, '_sortchannels')); + $i = $j = 0; + $data = array( + 'caption' => 'Registered Channels:', + 'border' => true, + 'headline' => array('Channel', 'Alias', 'Summary') + ); + foreach ($registered as $channel) { + $data['data'][] = array($channel->getName(), + $channel->getAlias(), + $channel->getSummary()); + } + + if (count($registered) === 0) { + $data = '(no registered channels)'; + } + $this->ui->outputData($data, $command); + return true; + } + + function doUpdateAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $channels = $reg->getChannels(); + + $success = true; + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doUpdate('channel-update', + $options, + array($channel->getName())); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + $success = false; + } else { + $success &= $err; + } + } + } + return $success; + } + + function doInfo($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel specified"); + } + + $reg = &$this->config->getRegistry(); + $channel = strtolower($params[0]); + if ($reg->channelExists($channel)) { + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + } else { + if (strpos($channel, '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($channel, $this->ui, $tmpdir); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('Cannot open "' . $channel . + '" (' . $loc->getMessage() . ')'); + } else { + $contents = implode('', file($loc)); + } + } else { + if (!file_exists($params[0])) { + return $this->raiseError('Unknown channel "' . $channel . '"'); + } + + $fp = fopen($params[0], 'r'); + if (!$fp) { + return $this->raiseError('Cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $chan = new PEAR_ChannelFile; + $chan->fromXmlString($contents); + $chan->validate(); + if ($errs = $chan->getErrors(true)) { + foreach ($errs as $err) { + $this->ui->outputData($err['level'] . ': ' . $err['message']); + } + return $this->raiseError('Channel file "' . $params[0] . '" is not valid'); + } + } + + if (!$chan) { + return $this->raiseError('Serious error: Channel "' . $params[0] . + '" has a corrupted registry entry'); + } + + $channel = $chan->getName(); + $caption = 'Channel ' . $channel . ' Information:'; + $data1 = array( + 'caption' => $caption, + 'border' => true); + $data1['data']['server'] = array('Name and Server', $chan->getName()); + if ($chan->getAlias() != $chan->getName()) { + $data1['data']['alias'] = array('Alias', $chan->getAlias()); + } + + $data1['data']['summary'] = array('Summary', $chan->getSummary()); + $validate = $chan->getValidationPackage(); + $data1['data']['vpackage'] = array('Validation Package Name', $validate['_content']); + $data1['data']['vpackageversion'] = + array('Validation Package Version', $validate['attribs']['version']); + $d = array(); + $d['main'] = $data1; + + $data['data'] = array(); + $data['caption'] = 'Server Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST()) { + if ($chan->supportsREST()) { + $funcs = $chan->getFunctions('rest'); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + + $d['protocols'] = $data; + $data['data'] = array(); + $mirrors = $chan->getMirrors(); + if ($mirrors) { + $data['caption'] = 'Channel ' . $channel . ' Mirrors:'; + unset($data['headline']); + foreach ($mirrors as $mirror) { + $data['data'][] = array($mirror['attribs']['host']); + $d['mirrors'] = $data; + } + + foreach ($mirrors as $i => $mirror) { + $data['data'] = array(); + $data['caption'] = 'Mirror ' . $mirror['attribs']['host'] . ' Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST($mirror['attribs']['host'])) { + if ($chan->supportsREST($mirror['attribs']['host'])) { + $funcs = $chan->getFunctions('rest', $mirror['attribs']['host']); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + $d['mirrorprotocols' . $i] = $data; + } + } + $this->ui->outputData($d, 'channel-info'); + } + + // }}} + + function doDelete($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-delete: no channel specified'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0])) { + return $this->raiseError('channel-delete: channel "' . $params[0] . '" does not exist'); + } + + $channel = $reg->channelName($params[0]); + if ($channel == 'pear.php.net') { + return $this->raiseError('Cannot delete the pear.php.net channel'); + } + + if ($channel == 'pecl.php.net') { + return $this->raiseError('Cannot delete the pecl.php.net channel'); + } + + if ($channel == 'doc.php.net') { + return $this->raiseError('Cannot delete the doc.php.net channel'); + } + + if ($channel == '__uri') { + return $this->raiseError('Cannot delete the __uri pseudo-channel'); + } + + if (PEAR::isError($err = $reg->listPackages($channel))) { + return $err; + } + + if (count($err)) { + return $this->raiseError('Channel "' . $channel . + '" has installed packages, cannot delete'); + } + + if (!$reg->deleteChannel($channel)) { + return $this->raiseError('Channel "' . $channel . '" deletion failed'); + } else { + $this->config->deleteChannel($channel); + $this->ui->outputData('Channel "' . $channel . '" deleted', $command); + } + } + + function doAdd($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-add: no channel file specified'); + } + + if (strpos($params[0], '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($params[0], $this->ui, $tmpdir, null, false); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('channel-add: Cannot open "' . $params[0] . + '" (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $lastmodified = $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError('channel-add: cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $result = $channel->fromXmlString($contents); + PEAR::staticPopErrorHandling(); + if (!$result) { + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('channel-add: invalid channel.xml file'); + } + } + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel->getName())) { + return $this->raiseError('channel-add: Channel "' . $channel->getName() . + '" exists, use channel-update to update entry', PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + $ret = $reg->addChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('channel-add: adding Channel "' . $channel->getName() . + '" to registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Adding Channel "' . $channel->getName() . '" succeeded', $command); + } + + function doUpdate($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel file specified"); + } + + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + $reg = &$this->config->getRegistry(); + $lastmodified = false; + if ((!file_exists($params[0]) || is_dir($params[0])) + && $reg->channelExists(strtolower($params[0]))) { + $c = $reg->getChannel(strtolower($params[0])); + if (PEAR::isError($c)) { + return $this->raiseError($c); + } + + $this->ui->outputData("Updating channel \"$params[0]\"", $command); + $dl = &$this->getDownloader(array()); + // if force is specified, use a timestamp of "1" to force retrieval + $lastmodified = isset($options['force']) ? false : $c->lastModified(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('http://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + // Attempt to fall back to https + $this->ui->outputData("Channel \"$params[0]\" is not responding over http://, failed with message: " . $contents->getMessage()); + $this->ui->outputData("Trying channel \"$params[0]\" over https:// instead"); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('https://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + return $this->raiseError('Cannot retrieve channel.xml for channel "' . + $c->getName() . '" (' . $contents->getMessage() . ')'); + } + } + + list($contents, $lastmodified) = $contents; + if (!$contents) { + $this->ui->outputData("Channel \"$params[0]\" is up to date"); + return; + } + + $contents = implode('', file($contents)); + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + if (!$channel->getErrors()) { + // security check: is the downloaded file for the channel we got it from? + if (strtolower($channel->getName()) != strtolower($c->getName())) { + if (!isset($options['force'])) { + return $this->raiseError('ERROR: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + + $this->ui->log(0, 'WARNING: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + } + } else { + if (strpos($params[0], '://')) { + $dl = &$this->getDownloader(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $dl->downloadHttp($params[0], + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError("Cannot open " . $params[0] . + ' (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError("Cannot open " . $params[0]); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + } + + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('Invalid channel.xml file'); + } + } + + if (!$reg->channelExists($channel->getName())) { + return $this->raiseError('Error: Channel "' . $channel->getName() . + '" does not exist, use channel-add to add an entry'); + } + + $ret = $reg->updateChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('Updating Channel "' . $channel->getName() . + '" in registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Update of Channel "' . $channel->getName() . '" succeeded'); + } + + function &getDownloader() + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = new PEAR_Downloader($this->ui, array(), $this->config); + return $a; + } + + function doAlias($command, $options, $params) + { + if (count($params) === 1) { + return $this->raiseError('No channel alias specified'); + } + + if (count($params) !== 2 || (!empty($params[1]) && $params[1]{0} == '-')) { + return $this->raiseError( + 'Invalid format, correct is: channel-alias channel alias'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0], true)) { + $extra = ''; + if ($reg->isAlias($params[0])) { + $extra = ' (use "channel-alias ' . $reg->channelName($params[0]) . ' ' . + strtolower($params[1]) . '")'; + } + + return $this->raiseError('"' . $params[0] . '" is not a valid channel' . $extra); + } + + if ($reg->isAlias($params[1])) { + return $this->raiseError('Channel "' . $reg->channelName($params[1]) . '" is ' . + 'already aliased to "' . strtolower($params[1]) . '", cannot re-alias'); + } + + $chan = &$reg->getChannel($params[0]); + if (PEAR::isError($chan)) { + return $this->raiseError('Corrupt registry? Error retrieving channel "' . $params[0] . + '" information (' . $chan->getMessage() . ')'); + } + + // make it a local alias + if (!$chan->setAlias(strtolower($params[1]), true)) { + return $this->raiseError('Alias "' . strtolower($params[1]) . + '" is not a valid channel alias'); + } + + $reg->updateChannel($chan); + $this->ui->outputData('Channel "' . $chan->getName() . '" aliased successfully to "' . + strtolower($params[1]) . '"'); + } + + /** + * The channel-discover command + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters. + * $params[0] should contain a string with either: + * - <channel name> or + * - <username>:<password>@<channel name> + * @return null|PEAR_Error + */ + function doDiscover($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel server specified"); + } + + // Look for the possible input format "<username>:<password>@<channel>" + if (preg_match('/^(.+):(.+)@(.+)\\z/', $params[0], $matches)) { + $username = $matches[1]; + $password = $matches[2]; + $channel = $matches[3]; + } else { + $channel = $params[0]; + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel)) { + if (!$reg->isAlias($channel)) { + return $this->raiseError("Channel \"$channel\" is already initialized", PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + return $this->raiseError("A channel alias named \"$channel\" " . + 'already exists, aliasing channel "' . $reg->channelName($channel) + . '"'); + } + + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('http://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + if ($err->getCode() === PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + // Attempt fetch via https + $this->ui->outputData("Discovering channel $channel over http:// failed with message: " . $err->getMessage()); + $this->ui->outputData("Trying to discover channel $channel over https:// instead"); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('https://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + } + + // Store username/password if they were given + // Arguably we should do a logintest on the channel here, but since + // that's awkward on a REST-based channel (even "pear login" doesn't + // do it for those), and XML-RPC is deprecated, it's fairly pointless. + if (isset($username)) { + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + $this->config->store(); + $this->ui->outputData("Stored login for channel \"$channel\" using username \"$username\"", $command); + } + + $this->ui->outputData("Discovery of channel \"$channel\" succeeded", $command); + } + + /** + * Execute the 'login' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogin($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $username = $this->config->get('username', null, $channel); + if (empty($username)) { + $username = isset($_ENV['USER']) ? $_ENV['USER'] : null; + } + $this->ui->outputData("Logging in to $server.", $command); + + list($username, $password) = $this->ui->userDialog( + $command, + array('Username', 'Password'), + array('text', 'password'), + array($username, '') + ); + $username = trim($username); + $password = trim($password); + + $ourfile = $this->config->getConfFile('user'); + if (!$ourfile) { + $ourfile = $this->config->getConfFile('system'); + } + + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + + if ($chan->supportsREST()) { + $ok = true; + } + + if ($ok !== true) { + return $this->raiseError('Login failed!'); + } + + $this->ui->outputData("Logged in.", $command); + // avoid changing any temporary settings changed with -d + $ourconfig = new PEAR_Config($ourfile, $ourfile); + $ourconfig->set('username', $username, 'user', $channel); + $ourconfig->set('password', $password, 'user', $channel); + $ourconfig->store(); + + return true; + } + + /** + * Execute the 'logout' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogout($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $this->ui->outputData("Logging out from $server.", $command); + $this->config->remove('username', 'user', $channel); + $this->config->remove('password', 'user', $channel); + $this->config->store(); + return true; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Channels.xml b/includes/pear/PEAR/Command/Channels.xml new file mode 100644 index 0000000..47b7206 --- /dev/null +++ b/includes/pear/PEAR/Command/Channels.xml @@ -0,0 +1,123 @@ +<commands version="1.0"> + <list-channels> + <summary>List Available Channels</summary> + <function>doList</function> + <shortcut>lc</shortcut> + <options /> + <doc> +List all available channels for installation. +</doc> + </list-channels> + <update-channels> + <summary>Update the Channel List</summary> + <function>doUpdateAll</function> + <shortcut>uc</shortcut> + <options /> + <doc> +List all installed packages in all channels. +</doc> + </update-channels> + <channel-delete> + <summary>Remove a Channel From the List</summary> + <function>doDelete</function> + <shortcut>cde</shortcut> + <options /> + <doc><channel name> +Delete a channel from the registry. You may not +remove any channel that has installed packages. +</doc> + </channel-delete> + <channel-add> + <summary>Add a Channel</summary> + <function>doAdd</function> + <shortcut>ca</shortcut> + <options /> + <doc><channel.xml> +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. +</doc> + </channel-add> + <channel-update> + <summary>Update an Existing Channel</summary> + <function>doUpdate</function> + <shortcut>cu</shortcut> + <options> + <force> + <shortopt>f</shortopt> + <doc>will force download of new channel.xml if an existing channel name is used</doc> + </force> + <channel> + <shortopt>c</shortopt> + <doc>will force download of new channel.xml if an existing channel name is used</doc> + <arg>CHANNEL</arg> + </channel> + </options> + <doc>[<channel.xml>|<channel name>] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. +</doc> + </channel-update> + <channel-info> + <summary>Retrieve Information on a Channel</summary> + <function>doInfo</function> + <shortcut>ci</shortcut> + <options /> + <doc><package> +List the files in an installed package. +</doc> + </channel-info> + <channel-alias> + <summary>Specify an alias to a channel name</summary> + <function>doAlias</function> + <shortcut>cha</shortcut> + <options /> + <doc><channel> <alias> +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. +</doc> + </channel-alias> + <channel-discover> + <summary>Initialize a Channel from its server</summary> + <function>doDiscover</function> + <shortcut>di</shortcut> + <options /> + <doc>[<channel.xml>|<channel name>] +Initialize a channel from its server and create a local channel.xml. +If <channel name> is in the format "<username>:<password>@<channel>" then +<username> and <password> will be set as the login username/password for +<channel>. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system's process list. +</doc> + </channel-discover> + <channel-login> + <summary>Connects and authenticates to remote channel server</summary> + <function>doLogin</function> + <shortcut>cli</shortcut> + <options /> + <doc><channel name> +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.</doc> + </channel-login> + <channel-logout> + <summary>Logs out from the remote channel server</summary> + <function>doLogout</function> + <shortcut>clo</shortcut> + <options /> + <doc><channel name> +Logs out from a remote channel server. If <channel name> is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration.</doc> + </channel-logout> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Common.php b/includes/pear/PEAR/Command/Common.php new file mode 100644 index 0000000..69b5cf2 --- /dev/null +++ b/includes/pear/PEAR/Command/Common.php @@ -0,0 +1,273 @@ +<?php +/** + * PEAR_Command_Common base class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR.php'; + +/** + * PEAR commands base class + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Common extends PEAR +{ + /** + * PEAR_Config object used to pass user system and configuration + * on when executing commands + * + * @var PEAR_Config + */ + var $config; + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * User Interface object, for all interaction with the user. + * @var object + */ + var $ui; + + var $_deps_rel_trans = array( + 'lt' => '<', + 'le' => '<=', + 'eq' => '=', + 'ne' => '!=', + 'gt' => '>', + 'ge' => '>=', + 'has' => '==' + ); + + var $_deps_type_trans = array( + 'pkg' => 'package', + 'ext' => 'extension', + 'php' => 'PHP', + 'prog' => 'external program', + 'ldlib' => 'external library for linking', + 'rtlib' => 'external runtime library', + 'os' => 'operating system', + 'websrv' => 'web server', + 'sapi' => 'SAPI backend' + ); + + /** + * PEAR_Command_Common constructor. + * + * @access public + */ + function PEAR_Command_Common(&$ui, &$config) + { + parent::PEAR(); + $this->config = &$config; + $this->ui = &$ui; + } + + /** + * Return a list of all the commands defined by this class. + * @return array list of commands + * @access public + */ + function getCommands() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + $ret[$command] = $this->commands[$command]['summary']; + } + + return $ret; + } + + /** + * Return a list of all the command shortcuts defined by this class. + * @return array shortcut => command + * @access public + */ + function getShortcuts() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + if (isset($this->commands[$command]['shortcut'])) { + $ret[$this->commands[$command]['shortcut']] = $command; + } + } + + return $ret; + } + + function getOptions($command) + { + $shortcuts = $this->getShortcuts(); + if (isset($shortcuts[$command])) { + $command = $shortcuts[$command]; + } + + if (isset($this->commands[$command]) && + isset($this->commands[$command]['options'])) { + return $this->commands[$command]['options']; + } + + return null; + } + + function getGetoptArgs($command, &$short_args, &$long_args) + { + $short_args = ''; + $long_args = array(); + if (empty($this->commands[$command]) || empty($this->commands[$command]['options'])) { + return; + } + + reset($this->commands[$command]['options']); + while (list($option, $info) = each($this->commands[$command]['options'])) { + $larg = $sarg = ''; + if (isset($info['arg'])) { + if ($info['arg']{0} == '(') { + $larg = '=='; + $sarg = '::'; + $arg = substr($info['arg'], 1, -1); + } else { + $larg = '='; + $sarg = ':'; + $arg = $info['arg']; + } + } + + if (isset($info['shortopt'])) { + $short_args .= $info['shortopt'] . $sarg; + } + + $long_args[] = $option . $larg; + } + } + + /** + * Returns the help message for the given command + * + * @param string $command The command + * @return mixed A fail string if the command does not have help or + * a two elements array containing [0]=>help string, + * [1]=> help string for the accepted cmd args + */ + function getHelp($command) + { + $config = &PEAR_Config::singleton(); + if (!isset($this->commands[$command])) { + return "No such command \"$command\""; + } + + $help = null; + if (isset($this->commands[$command]['doc'])) { + $help = $this->commands[$command]['doc']; + } + + if (empty($help)) { + // XXX (cox) Fallback to summary if there is no doc (show both?) + if (!isset($this->commands[$command]['summary'])) { + return "No help for command \"$command\""; + } + $help = $this->commands[$command]['summary']; + } + + if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) { + foreach($matches[0] as $k => $v) { + $help = preg_replace("/$v/", $config->get($matches[1][$k]), $help); + } + } + + return array($help, $this->getHelpArgs($command)); + } + + /** + * Returns the help for the accepted arguments of a command + * + * @param string $command + * @return string The help string + */ + function getHelpArgs($command) + { + if (isset($this->commands[$command]['options']) && + count($this->commands[$command]['options'])) + { + $help = "Options:\n"; + foreach ($this->commands[$command]['options'] as $k => $v) { + if (isset($v['arg'])) { + if ($v['arg'][0] == '(') { + $arg = substr($v['arg'], 1, -1); + $sapp = " [$arg]"; + $lapp = "[=$arg]"; + } else { + $sapp = " $v[arg]"; + $lapp = "=$v[arg]"; + } + } else { + $sapp = $lapp = ""; + } + + if (isset($v['shortopt'])) { + $s = $v['shortopt']; + $help .= " -$s$sapp, --$k$lapp\n"; + } else { + $help .= " --$k$lapp\n"; + } + + $p = " "; + $doc = rtrim(str_replace("\n", "\n$p", $v['doc'])); + $help .= " $doc\n"; + } + + return $help; + } + + return null; + } + + function run($command, $options, $params) + { + if (empty($this->commands[$command]['function'])) { + // look for shortcuts + foreach (array_keys($this->commands) as $cmd) { + if (isset($this->commands[$cmd]['shortcut']) && $this->commands[$cmd]['shortcut'] == $command) { + if (empty($this->commands[$cmd]['function'])) { + return $this->raiseError("unknown command `$command'"); + } else { + $func = $this->commands[$cmd]['function']; + } + $command = $cmd; + + //$command = $this->commands[$cmd]['function']; + break; + } + } + } else { + $func = $this->commands[$command]['function']; + } + + return $this->$func($command, $options, $params); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Config.php b/includes/pear/PEAR/Command/Config.php new file mode 100644 index 0000000..8561ee5 --- /dev/null +++ b/includes/pear/PEAR/Command/Config.php @@ -0,0 +1,414 @@ +<?php +/** + * PEAR_Command_Config (config-show, config-get, config-set, config-help, config-create commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for managing configuration data. + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Config extends PEAR_Command_Common +{ + var $commands = array( + 'config-show' => array( + 'summary' => 'Show All Settings', + 'function' => 'doConfigShow', + 'shortcut' => 'csh', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => '[layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. +', + ), + 'config-get' => array( + 'summary' => 'Show One Setting', + 'function' => 'doConfigGet', + 'shortcut' => 'cg', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => '<parameter> [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. +', + ), + 'config-set' => array( + 'summary' => 'Change Setting', + 'function' => 'doConfigSet', + 'shortcut' => 'cs', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => '<parameter> <value> [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. +', + ), + 'config-help' => array( + 'summary' => 'Show Information About Setting', + 'function' => 'doConfigHelp', + 'shortcut' => 'ch', + 'options' => array(), + 'doc' => '[parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. +', + ), + 'config-create' => array( + 'summary' => 'Create a Default configuration file', + 'function' => 'doConfigCreate', + 'shortcut' => 'coc', + 'options' => array( + 'windows' => array( + 'shortopt' => 'w', + 'doc' => 'create a config file for a windows install', + ), + ), + 'doc' => '<root path> <filename> +Create a default configuration file with all directory configuration +variables set to subdirectories of <root path>, and save it as <filename>. +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). +', + ), + ); + + /** + * PEAR_Command_Config constructor. + * + * @access public + */ + function PEAR_Command_Config(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doConfigShow($command, $options, $params) + { + $layer = null; + if (is_array($params)) { + $layer = isset($params[0]) ? $params[0] : null; + } + + // $params[0] -> the layer + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-show:$error"); + } + + $keys = $this->config->getKeys(); + sort($keys); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); + foreach ($keys as $key) { + $type = $this->config->getType($key); + $value = $this->config->get($key, $layer, $channel); + if ($type == 'password' && $value) { + $value = '********'; + } + + if ($value === false) { + $value = 'false'; + } elseif ($value === true) { + $value = 'true'; + } + + $data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value); + } + + foreach ($this->config->getLayers() as $layer) { + $data['data']['Config Files'][] = array(ucfirst($layer) . ' Configuration File', 'Filename' , $this->config->getConfFile($layer)); + } + + $this->ui->outputData($data, $command); + return true; + } + + function doConfigGet($command, $options, $params) + { + $args_cnt = is_array($params) ? count($params) : 0; + switch ($args_cnt) { + case 1: + $config_key = $params[0]; + $layer = null; + break; + case 2: + $config_key = $params[0]; + $layer = $params[1]; + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-get:$error"); + } + break; + case 0: + default: + return $this->raiseError("config-get expects 1 or 2 parameters"); + } + + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + $this->ui->outputData($this->config->get($config_key, $layer, $channel), $command); + return true; + } + + function doConfigSet($command, $options, $params) + { + // $param[0] -> a parameter to set + // $param[1] -> the value for the parameter + // $param[2] -> the layer + $failmsg = ''; + if (count($params) < 2 || count($params) > 3) { + $failmsg .= "config-set expects 2 or 3 parameters"; + return PEAR::raiseError($failmsg); + } + + if (isset($params[2]) && ($error = $this->_checkLayer($params[2]))) { + $failmsg .= $error; + return PEAR::raiseError("config-set:$failmsg"); + } + + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + if ($params[0] == 'default_channel' && !$reg->channelExists($params[1])) { + return $this->raiseError('Channel "' . $params[1] . '" does not exist'); + } + + if ($params[0] == 'preferred_mirror' + && ( + !$reg->mirrorExists($channel, $params[1]) && + (!$reg->channelExists($params[1]) || $channel != $params[1]) + ) + ) { + $msg = 'Channel Mirror "' . $params[1] . '" does not exist'; + $msg .= ' in your registry for channel "' . $channel . '".'; + $msg .= "\n" . 'Attempt to run "pear channel-update ' . $channel .'"'; + $msg .= ' if you believe this mirror should exist as you may'; + $msg .= ' have outdated channel information.'; + return $this->raiseError($msg); + } + + if (count($params) == 2) { + array_push($params, 'user'); + $layer = 'user'; + } else { + $layer = $params[2]; + } + + array_push($params, $channel); + if (!call_user_func_array(array(&$this->config, 'set'), $params)) { + array_pop($params); + $failmsg = "config-set (" . implode(", ", $params) . ") failed, channel $channel"; + } else { + $this->config->store($layer); + } + + if ($failmsg) { + return $this->raiseError($failmsg); + } + + $this->ui->outputData('config-set succeeded', $command); + return true; + } + + function doConfigHelp($command, $options, $params) + { + if (empty($params)) { + $params = $this->config->getKeys(); + } + + $data['caption'] = "Config help" . ((count($params) == 1) ? " for $params[0]" : ''); + $data['headline'] = array('Name', 'Type', 'Description'); + $data['border'] = true; + foreach ($params as $name) { + $type = $this->config->getType($name); + $docs = $this->config->getDocs($name); + if ($type == 'set') { + $docs = rtrim($docs) . "\nValid set: " . + implode(' ', $this->config->getSetValues($name)); + } + + $data['data'][] = array($name, $type, $docs); + } + + $this->ui->outputData($data, $command); + } + + function doConfigCreate($command, $options, $params) + { + if (count($params) != 2) { + return PEAR::raiseError('config-create: must have 2 parameters, root path and ' . + 'filename to save as'); + } + + $root = $params[0]; + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"), + array('/', '/', '/'), + $root); + if ($root{0} != '/') { + if (!isset($options['windows'])) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "/", was: "' . $root . '"'); + } + + if (!preg_match('/^[A-Za-z]:/', $root)) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "\\" or "C:\\", was: "' . $root . '"'); + } + } + + $windows = isset($options['windows']); + if ($windows) { + $root = str_replace('/', '\\', $root); + } + + if (!file_exists($params[1]) && !@touch($params[1])) { + return PEAR::raiseError('Could not create "' . $params[1] . '"'); + } + + $params[1] = realpath($params[1]); + $config = &new PEAR_Config($params[1], '#no#system#config#', false, false); + if ($root{strlen($root) - 1} == '/') { + $root = substr($root, 0, strlen($root) - 1); + } + + $config->noRegistry(); + $config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user'); + $config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data"); + $config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www"); + $config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg"); + $config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext"); + $config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs"); + $config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests"); + $config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache"); + $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download"); + $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp"); + $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear"); + $config->writeConfigFile(); + $this->_showConfig($config); + $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"', + $command); + } + + function _showConfig(&$config) + { + $params = array('user'); + $keys = $config->getKeys(); + sort($keys); + $channel = 'pear.php.net'; + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); + foreach ($keys as $key) { + $type = $config->getType($key); + $value = $config->get($key, 'user', $channel); + if ($type == 'password' && $value) { + $value = '********'; + } + + if ($value === false) { + $value = 'false'; + } elseif ($value === true) { + $value = 'true'; + } + $data['data'][$config->getGroup($key)][] = + array($config->getPrompt($key) , $key, $value); + } + + foreach ($config->getLayers() as $layer) { + $data['data']['Config Files'][] = + array(ucfirst($layer) . ' Configuration File', 'Filename' , + $config->getConfFile($layer)); + } + + $this->ui->outputData($data, 'config-show'); + return true; + } + + /** + * Checks if a layer is defined or not + * + * @param string $layer The layer to search for + * @return mixed False on no error or the error message + */ + function _checkLayer($layer = null) + { + if (!empty($layer) && $layer != 'default') { + $layers = $this->config->getLayers(); + if (!in_array($layer, $layers)) { + return " only the layers: \"" . implode('" or "', $layers) . "\" are supported"; + } + } + + return false; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Config.xml b/includes/pear/PEAR/Command/Config.xml new file mode 100644 index 0000000..f64a925 --- /dev/null +++ b/includes/pear/PEAR/Command/Config.xml @@ -0,0 +1,92 @@ +<commands version="1.0"> + <config-show> + <summary>Show All Settings</summary> + <function>doConfigShow</function> + <shortcut>csh</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>show configuration variables for another channel</doc> + <arg>CHAN</arg> + </channel> + </options> + <doc>[layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. +</doc> + </config-show> + <config-get> + <summary>Show One Setting</summary> + <function>doConfigGet</function> + <shortcut>cg</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>show configuration variables for another channel</doc> + <arg>CHAN</arg> + </channel> + </options> + <doc><parameter> [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. +</doc> + </config-get> + <config-set> + <summary>Change Setting</summary> + <function>doConfigSet</function> + <shortcut>cs</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>show configuration variables for another channel</doc> + <arg>CHAN</arg> + </channel> + </options> + <doc><parameter> <value> [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. +</doc> + </config-set> + <config-help> + <summary>Show Information About Setting</summary> + <function>doConfigHelp</function> + <shortcut>ch</shortcut> + <options /> + <doc>[parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. +</doc> + </config-help> + <config-create> + <summary>Create a Default configuration file</summary> + <function>doConfigCreate</function> + <shortcut>coc</shortcut> + <options> + <windows> + <shortopt>w</shortopt> + <doc>create a config file for a windows install</doc> + </windows> + </options> + <doc><root path> <filename> +Create a default configuration file with all directory configuration +variables set to subdirectories of <root path>, and save it as <filename>. +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). +</doc> + </config-create> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Install.php b/includes/pear/PEAR/Command/Install.php new file mode 100644 index 0000000..79e68db --- /dev/null +++ b/includes/pear/PEAR/Command/Install.php @@ -0,0 +1,1268 @@ +<?php +/** + * PEAR_Command_Install (install, upgrade, upgrade-all, uninstall, bundle, run-scripts commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for installation or deinstallation/upgrading of + * packages. + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Install extends PEAR_Command_Common +{ + // {{{ properties + + var $commands = array( + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'shortcut' => 'i', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as installed', + ), + 'soft' => array( + 'shortopt' => 's', + 'doc' => 'soft install, fail silently, or upgrade if already installed', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'packagingroot' => array( + 'shortopt' => 'P', + 'arg' => 'DIR', + 'doc' => 'root directory used when packaging files, like RPM packaging', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => '[channel/]<package> ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel\'s server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doInstall', + 'shortcut' => 'up', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => '<package> ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'upgrade-all' => array( + 'summary' => 'Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]', + 'function' => 'doUpgradeAll', + 'shortcut' => 'ua', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'loose' => array( + 'doc' => 'do not check for recommended dependency version', + ), + ), + 'doc' => ' +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'shortcut' => 'un', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to uninstall remotely', + ), + ), + 'doc' => '[channel/]<package> ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) +'), + 'bundle' => array( + 'summary' => 'Unpacks a Pecl Package', + 'function' => 'doBundle', + 'shortcut' => 'bun', + 'options' => array( + 'destination' => array( + 'shortopt' => 'd', + 'arg' => 'DIR', + 'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'Force the unpacking even if there were errors in the package', + ), + ), + 'doc' => '<package> +Unpacks a Pecl Package into the selected location. It will download the +package if needed. +'), + 'run-scripts' => array( + 'summary' => 'Run Post-Install Scripts bundled with a package', + 'function' => 'doRunScripts', + 'shortcut' => 'rs', + 'options' => array( + ), + 'doc' => '<package> +Run post-installation scripts in package <package>, if any exist. +'), + ); + + // }}} + // {{{ constructor + + /** + * PEAR_Command_Install constructor. + * + * @access public + */ + function PEAR_Command_Install(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + // }}} + + /** + * For unit testing purposes + */ + function &getDownloader(&$ui, $options, &$config) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($ui, $options, $config); + return $a; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + function enableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + // already enabled - assume if one is, all are + return true; + } + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + } else { + $newini = array(); + } + foreach ($binaries as $binary) { + if (!$this->config->get('ext_dir') && $ini['extension_dir']) { + $binary = basename($binary); + } + $newini[] = $enable . '="' . $binary . '"' . (OS_UNIX ? "\n" : "\r\n"); + } + $newini = array_merge($newini, array_slice($ini['all'], $line)); + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function disableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + $found = false; + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + $found = true; + break; + } + } + if (!$found) { + // not enabled + return true; + } + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + // delete the enable line + $newini = array_merge($newini, array_slice($ini['all'], $line + 1)); + } else { + $newini = array_slice($ini['all'], 1); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function _parseIni($filename) + { + if (!file_exists($filename)) { + return PEAR::raiseError('php.ini "' . $filename . '" does not exist'); + } + + if (filesize($filename) > 300000) { + return PEAR::raiseError('php.ini "' . $filename . '" is too large, aborting'); + } + + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $zend_extension_line = 'zend_extension' . $debug . $ts; + $all = @file($filename); + if (!$all) { + return PEAR::raiseError('php.ini "' . $filename .'" could not be read'); + } + $zend_extensions = $extensions = array(); + // assume this is right, but pull from the php.ini if it is found + $extension_dir = ini_get('extension_dir'); + foreach ($all as $linenum => $line) { + $line = trim($line); + if (!$line) { + continue; + } + if ($line[0] == ';') { + continue; + } + if (strtolower(substr($line, 0, 13)) == 'extension_dir') { + $line = trim(substr($line, 13)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extension_dir = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, 9)) == 'extension') { + $line = trim(substr($line, 9)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, strlen($zend_extension_line))) == + $zend_extension_line) { + $line = trim(substr($line, strlen($zend_extension_line))); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $zend_extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + } + return array( + 'extensions' => $extensions, + 'zend_extensions' => $zend_extensions, + 'extension_dir' => $extension_dir, + 'all' => $all, + ); + } + + // {{{ doInstall() + + function doInstall($command, $options, $params) + { + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + if (isset($options['installroot']) && isset($options['packagingroot'])) { + return $this->raiseError('ERROR: cannot use both --installroot and --packagingroot'); + } + + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if ($command == 'upgrade' || $command == 'upgrade-all') { + // If people run the upgrade command but pass nothing, emulate a upgrade-all + if ($command == 'upgrade' && empty($params)) { + return $this->doUpgradeAll($command, $options, $params); + } + $options['upgrade'] = true; + } else { + $packages = $params; + } + + $instreg = &$reg; // instreg used to check if package is installed + if (isset($options['packagingroot']) && !isset($options['upgrade'])) { + $packrootphp_dir = $this->installer->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $options['packagingroot']); + $instreg = new PEAR_Registry($packrootphp_dir); // other instreg! + + if ($this->config->get('verbose') > 2) { + $this->ui->outputData('using package root: ' . $options['packagingroot']); + } + } + + $abstractpackages = $otherpackages = array(); + // parse params + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($params as $param) { + if (strpos($param, 'http://') === 0) { + $otherpackages[] = $param; + continue; + } + + if (strpos($param, 'channel://') === false && @file_exists($param)) { + if (isset($options['force'])) { + $otherpackages[] = $param; + continue; + } + + $pkg = new PEAR_PackageFile($this->config); + $pf = $pkg->fromAnyFile($param, PEAR_VALIDATE_DOWNLOADING); + if (PEAR::isError($pf)) { + $otherpackages[] = $param; + continue; + } + + $exists = $reg->packageExists($pf->getPackage(), $pf->getChannel()); + $pversion = $reg->packageInfo($pf->getPackage(), 'version', $pf->getChannel()); + $version_compare = version_compare($pf->getVersion(), $pversion, '<='); + if ($exists && $version_compare) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString( + array('package' => $pf->getPackage(), + 'channel' => $pf->getChannel()), true)); + } + continue; + } + $otherpackages[] = $param; + continue; + } + + $e = $reg->parsePackageName($param, $channel); + if (PEAR::isError($e)) { + $otherpackages[] = $param; + } else { + $abstractpackages[] = $e; + } + } + PEAR::staticPopErrorHandling(); + + // if there are any local package .tgz or remote static url, we can't + // filter. The filter only works for abstract packages + if (count($abstractpackages) && !isset($options['force'])) { + // when not being forced, only do necessary upgrades/installs + if (isset($options['upgrade'])) { + $abstractpackages = $this->_filterUptodatePackages($abstractpackages, $command); + } else { + $count = count($abstractpackages); + foreach ($abstractpackages as $i => $package) { + if (isset($package['group'])) { + // do not filter out install groups + continue; + } + + if ($instreg->packageExists($package['package'], $package['channel'])) { + if ($count > 1) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString($package, true)); + } + unset($abstractpackages[$i]); + } elseif ($count === 1) { + // Lets try to upgrade it since it's already installed + $options['upgrade'] = true; + } + } + } + } + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } elseif (count($abstractpackages)) { + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } + + $packages = array_merge($abstractpackages, $otherpackages); + if (!count($packages)) { + $c = ''; + if (isset($options['channel'])){ + $c .= ' in channel "' . $options['channel'] . '"'; + } + $this->ui->outputData('Nothing to ' . $command . $c); + return true; + } + + $this->downloader = &$this->getDownloader($this->ui, $options, $this->config); + $errors = $downloaded = $binaries = array(); + $downloaded = &$this->downloader->download($packages); + if (PEAR::isError($downloaded)) { + return $this->raiseError($downloaded); + } + + $errors = $this->downloader->getErrorMsgs(); + if (count($errors)) { + $err = array(); + $err['data'] = array(); + foreach ($errors as $error) { + if ($error !== null) { + $err['data'][] = array($error); + } + } + + if (!empty($err['data'])) { + $err['headline'] = 'Install Errors'; + $this->ui->outputData($err); + } + + if (!count($downloaded)) { + return $this->raiseError("$command failed"); + } + } + + $data = array( + 'headline' => 'Packages that would be Installed' + ); + + if (isset($options['pretend'])) { + foreach ($downloaded as $package) { + $data['data'][] = array($reg->parsedPackageNameToString($package->getParsedPackage())); + } + $this->ui->outputData($data, 'pretend'); + return true; + } + + $this->installer->setOptions($options); + $this->installer->sortPackagesForInstall($downloaded); + if (PEAR::isError($err = $this->installer->setDownloadedPackages($downloaded))) { + $this->raiseError($err->getMessage()); + return true; + } + + $binaries = $extrainfo = array(); + foreach ($downloaded as $param) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->install($param, $options); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $oldinfo = $info; + $pkg = &$param->getPackageFile(); + if ($info->getCode() != PEAR_INSTALLER_NOBINARY) { + if (!($info = $pkg->installBinary($this->installer))) { + $this->ui->outputData('ERROR: ' .$oldinfo->getMessage()); + continue; + } + + // we just installed a different package than requested, + // let's change the param and info so that the rest of this works + $param = $info[0]; + $info = $info[1]; + } + } + + if (!is_array($info)) { + return $this->raiseError("$command failed"); + } + + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin' || + $param->getPackageType() == 'zendextsrc' || + $param->getPackageType() == 'zendextbin' + ) { + $pkg = &$param->getPackageFile(); + if ($instbin = $pkg->getInstalledBinary()) { + $instpkg = &$instreg->getPackage($instbin, $pkg->getChannel()); + } else { + $instpkg = &$instreg->getPackage($pkg->getPackage(), $pkg->getChannel()); + } + + foreach ($instpkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h')) + ) { + continue; // make sure we don't match php_blah.h + } + + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->enableExtension(array($pinfo[0]), $param->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $extrainfo[] = 'You should add "' . $exttype . '=' . + $pinfo[1]['basename'] . '" to php.ini'; + } else { + $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() . + ' enabled in php.ini'; + } + } + } + } + + if ($this->config->get('verbose') > 0) { + $chan = $param->getChannel(); + $label = $reg->parsedPackageNameToString( + array( + 'channel' => $chan, + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + )); + $out = array('data' => "$command ok: $label"); + if (isset($info['release_warnings'])) { + $out['release_warnings'] = $info['release_warnings']; + } + $this->ui->outputData($out, $command); + + if (!isset($options['register-only']) && !isset($options['offline'])) { + if ($this->config->isDefinedLayer('ftp')) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpInstall($param); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote install failed: $label"); + } else { + $this->ui->outputData("remote install ok: $label"); + } + } + } + } + + $deps = $param->getDeps(); + if ($deps) { + if (isset($deps['group'])) { + $groups = $deps['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + + foreach ($groups as $group) { + if ($group['attribs']['name'] == 'default') { + // default group is always installed, unless the user + // explicitly chooses to install another group + continue; + } + $extrainfo[] = $param->getPackage() . ': Optional feature ' . + $group['attribs']['name'] . ' available (' . + $group['attribs']['hint'] . ')'; + } + + $extrainfo[] = $param->getPackage() . + ': To install optional features use "pear install ' . + $reg->parsedPackageNameToString( + array('package' => $param->getPackage(), + 'channel' => $param->getChannel()), true) . + '#featurename"'; + } + } + + $pkg = &$instreg->getPackage($param->getPackage(), $param->getChannel()); + // $pkg may be NULL if install is a 'fake' install via --packagingroot + if (is_object($pkg)) { + $pkg->setConfig($this->config); + if ($list = $pkg->listPostinstallScripts()) { + $pn = $reg->parsedPackageNameToString(array('channel' => + $param->getChannel(), 'package' => $param->getPackage()), true); + $extrainfo[] = $pn . ' has post-install scripts:'; + foreach ($list as $file) { + $extrainfo[] = $file; + } + $extrainfo[] = $param->getPackage() . + ': Use "pear run-scripts ' . $pn . '" to finish setup.'; + $extrainfo[] = 'DO NOT RUN SCRIPTS FROM UNTRUSTED SOURCES'; + } + } + } + + if (count($extrainfo)) { + foreach ($extrainfo as $info) { + $this->ui->outputData($info); + } + } + + return true; + } + + // }}} + // {{{ doUpgradeAll() + + function doUpgradeAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $upgrade = array(); + + if (isset($options['channel'])) { + $channels = array($options['channel']); + } else { + $channels = $reg->listChannels(); + } + + foreach ($channels as $channel) { + if ($channel == '__uri') { + continue; + } + + // parse name with channel + foreach ($reg->listPackages($channel) as $name) { + $upgrade[] = $reg->parsedPackageNameToString(array( + 'channel' => $channel, + 'package' => $name + )); + } + } + + $err = $this->doInstall($command, $options, $upgrade); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + } + } + + // }}} + // {{{ doUninstall() + + function doUninstall($command, $options, $params) + { + if (count($params) < 1) { + return $this->raiseError("Please supply the package(s) you want to uninstall"); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if (isset($options['remoteconfig'])) { + $e = $this->config->readFTPConfigFile($options['remoteconfig']); + if (!PEAR::isError($e)) { + $this->installer->setConfig($this->config); + } + } + + $reg = &$this->config->getRegistry(); + $newparams = array(); + $binaries = array(); + $badparams = array(); + foreach ($params as $pkg) { + $channel = $this->config->get('default_channel'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($pkg, $channel); + PEAR::staticPopErrorHandling(); + if (!$parsed || PEAR::isError($parsed)) { + $badparams[] = $pkg; + continue; + } + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = &$reg->getPackage($package, $channel); + if ($info === null && + ($channel == 'pear.php.net' || $channel == 'pecl.php.net')) { + // make sure this isn't a package that has flipped from pear to pecl but + // used a package.xml 1.0 + $testc = ($channel == 'pear.php.net') ? 'pecl.php.net' : 'pear.php.net'; + $info = &$reg->getPackage($package, $testc); + if ($info !== null) { + $channel = $testc; + } + } + if ($info === null) { + $badparams[] = $pkg; + } else { + $newparams[] = &$info; + // check for binary packages (this is an alias for those packages if so) + if ($installedbinary = $info->getInstalledBinary()) { + $this->ui->log('adding binary package ' . + $reg->parsedPackageNameToString(array('channel' => $channel, + 'package' => $installedbinary), true)); + $newparams[] = &$reg->getPackage($installedbinary, $channel); + } + // add the contents of a dependency group to the list of installed packages + if (isset($parsed['group'])) { + $group = $info->getDependencyGroup($parsed['group']); + if ($group) { + $installed = $reg->getInstalledGroup($group); + if ($installed) { + foreach ($installed as $i => $p) { + $newparams[] = &$installed[$i]; + } + } + } + } + } + } + $err = $this->installer->sortPackagesForUninstall($newparams); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + return true; + } + $params = $newparams; + // twist this to use it to check on whether dependent packages are also being uninstalled + // for circular dependencies like subpackages + $this->installer->setUninstallPackages($newparams); + $params = array_merge($params, $badparams); + $binaries = array(); + foreach ($params as $pkg) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + if ($err = $this->installer->uninstall($pkg, $options)) { + $this->installer->popErrorHandling(); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + continue; + } + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin' || + $pkg->getPackageType() == 'zendextsrc' || + $pkg->getPackageType() == 'zendextbin') { + if ($instbin = $pkg->getInstalledBinary()) { + continue; // this will be uninstalled later + } + + foreach ($pkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->disableExtension(array($pinfo[0]), $pkg->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $this->ui->outputData('Unable to remove "' . $exttype . '=' . + $pinfo[1]['basename'] . '" from php.ini', $command); + } else { + $this->ui->outputData('Extension ' . $pkg->getProvidesExtension() . + ' disabled in php.ini', $command); + } + } + } + } + $savepkg = $pkg; + if ($this->config->get('verbose') > 0) { + if (is_object($pkg)) { + $pkg = $reg->parsedPackageNameToString($pkg); + } + $this->ui->outputData("uninstall ok: $pkg", $command); + } + if (!isset($options['offline']) && is_object($savepkg) && + defined('PEAR_REMOTEINSTALL_OK')) { + if ($this->config->isDefinedLayer('ftp')) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpUninstall($savepkg); + $this->installer->popErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote uninstall failed: $pkg"); + } else { + $this->ui->outputData("remote uninstall ok: $pkg"); + } + } + } + } else { + $this->installer->popErrorHandling(); + if (!is_object($pkg)) { + return $this->raiseError("uninstall failed: $pkg"); + } + $pkg = $reg->parsedPackageNameToString($pkg); + } + } + + return true; + } + + // }}} + + + // }}} + // {{{ doBundle() + /* + (cox) It just downloads and untars the package, does not do + any check that the PEAR_Installer::_installFile() does. + */ + + function doBundle($command, $options, $params) + { + $opts = array( + 'force' => true, + 'nodeps' => true, + 'soft' => true, + 'downloadonly' => true + ); + $downloader = &$this->getDownloader($this->ui, $opts, $this->config); + $reg = &$this->config->getRegistry(); + if (count($params) < 1) { + return $this->raiseError("Please supply the package you want to bundle"); + } + + if (isset($options['destination'])) { + if (!is_dir($options['destination'])) { + System::mkdir('-p ' . $options['destination']); + } + $dest = realpath($options['destination']); + } else { + $pwd = getcwd(); + $dir = $pwd . DIRECTORY_SEPARATOR . 'ext'; + $dest = is_dir($dir) ? $dir : $pwd; + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $downloader->setDownloadDir($dest); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('download directory "' . $dest . + '" is not writeable.'); + } + $result = &$downloader->download(array($params[0])); + if (PEAR::isError($result)) { + return $result; + } + if (!isset($result[0])) { + return $this->raiseError('unable to unpack ' . $params[0]); + } + $pkgfile = &$result[0]->getPackageFile(); + $pkgname = $pkgfile->getName(); + $pkgversion = $pkgfile->getVersion(); + + // Unpacking ------------------------------------------------- + $dest .= DIRECTORY_SEPARATOR . $pkgname; + $orig = $pkgname . '-' . $pkgversion; + + $tar = &new Archive_Tar($pkgfile->getArchiveFile()); + if (!$tar->extractModify($dest, $orig)) { + return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile()); + } + $this->ui->outputData("Package ready at '$dest'"); + // }}} + } + + // }}} + + function doRunScripts($command, $options, $params) + { + if (!isset($params[0])) { + return $this->raiseError('run-scripts expects 1 parameter: a package name'); + } + + $reg = &$this->config->getRegistry(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = &$reg->getPackage($parsed['package'], $parsed['channel']); + if (!is_object($package)) { + return $this->raiseError('Could not retrieve package "' . $params[0] . '" from registry'); + } + + $package->setConfig($this->config); + $package->runPostinstallScripts(); + $this->ui->outputData('Install scripts complete', $command); + return true; + } + + /** + * Given a list of packages, filter out those ones that are already up to date + * + * @param $packages: packages, in parsed array format ! + * @return list of packages that can be upgraded + */ + function _filterUptodatePackages($packages, $command) + { + $reg = &$this->config->getRegistry(); + $latestReleases = array(); + + $ret = array(); + foreach ($packages as $package) { + if (isset($package['group'])) { + $ret[] = $package; + continue; + } + + $channel = $package['channel']; + $name = $package['package']; + if (!$reg->packageExists($name, $channel)) { + $ret[] = $package; + continue; + } + + if (!isset($latestReleases[$channel])) { + // fill in cache for this channel + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror', null, $channel); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + $dorest = true; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!isset($package['state'])) { + $state = $this->config->get('preferred_state', null, $channel); + } else { + $state = $package['state']; + } + + if ($dorest) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + $installed = array_flip($reg->listPackages($channel)); + $latest = $rest->listLatestUpgrades($base, $state, $installed, $channel, $reg); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($latest)) { + $this->ui->outputData('Error getting channel info from ' . $channel . + ': ' . $latest->getMessage()); + continue; + } + + $latestReleases[$channel] = array_change_key_case($latest); + } + + // check package for latest release + $name_lower = strtolower($name); + if (isset($latestReleases[$channel][$name_lower])) { + // if not set, up to date + $inst_version = $reg->packageInfo($name, 'version', $channel); + $channel_version = $latestReleases[$channel][$name_lower]['version']; + if (version_compare($channel_version, $inst_version, 'le')) { + // installed version is up-to-date + continue; + } + + // maintain BC + if ($command == 'upgrade-all') { + $this->ui->outputData(array('data' => 'Will upgrade ' . + $reg->parsedPackageNameToString($package)), $command); + } + $ret[] = $package; + } + } + + return $ret; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Install.xml b/includes/pear/PEAR/Command/Install.xml new file mode 100644 index 0000000..1b1e933 --- /dev/null +++ b/includes/pear/PEAR/Command/Install.xml @@ -0,0 +1,276 @@ +<commands version="1.0"> + <install> + <summary>Install Package</summary> + <function>doInstall</function> + <shortcut>i</shortcut> + <options> + <force> + <shortopt>f</shortopt> + <doc>will overwrite newer installed packages</doc> + </force> + <loose> + <shortopt>l</shortopt> + <doc>do not check for recommended dependency version</doc> + </loose> + <nodeps> + <shortopt>n</shortopt> + <doc>ignore dependencies, install anyway</doc> + </nodeps> + <register-only> + <shortopt>r</shortopt> + <doc>do not install files, only register the package as installed</doc> + </register-only> + <soft> + <shortopt>s</shortopt> + <doc>soft install, fail silently, or upgrade if already installed</doc> + </soft> + <nobuild> + <shortopt>B</shortopt> + <doc>don't build C extensions</doc> + </nobuild> + <nocompress> + <shortopt>Z</shortopt> + <doc>request uncompressed files when downloading</doc> + </nocompress> + <installroot> + <shortopt>R</shortopt> + <doc>root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM</doc> + <arg>DIR</arg> + </installroot> + <packagingroot> + <shortopt>P</shortopt> + <doc>root directory used when packaging files, like RPM packaging</doc> + <arg>DIR</arg> + </packagingroot> + <ignore-errors> + <shortopt></shortopt> + <doc>force install even if there were errors</doc> + </ignore-errors> + <alldeps> + <shortopt>a</shortopt> + <doc>install all required and optional dependencies</doc> + </alldeps> + <onlyreqdeps> + <shortopt>o</shortopt> + <doc>install all required dependencies</doc> + </onlyreqdeps> + <offline> + <shortopt>O</shortopt> + <doc>do not attempt to download any urls or contact channels</doc> + </offline> + <pretend> + <shortopt>p</shortopt> + <doc>Only list the packages that would be downloaded</doc> + </pretend> + </options> + <doc>[channel/]<package> ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel's server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +</doc> + </install> + <upgrade> + <summary>Upgrade Package</summary> + <function>doInstall</function> + <shortcut>up</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>upgrade packages from a specific channel</doc> + <arg>CHAN</arg> + </channel> + <force> + <shortopt>f</shortopt> + <doc>overwrite newer installed packages</doc> + </force> + <loose> + <shortopt>l</shortopt> + <doc>do not check for recommended dependency version</doc> + </loose> + <nodeps> + <shortopt>n</shortopt> + <doc>ignore dependencies, upgrade anyway</doc> + </nodeps> + <register-only> + <shortopt>r</shortopt> + <doc>do not install files, only register the package as upgraded</doc> + </register-only> + <nobuild> + <shortopt>B</shortopt> + <doc>don't build C extensions</doc> + </nobuild> + <nocompress> + <shortopt>Z</shortopt> + <doc>request uncompressed files when downloading</doc> + </nocompress> + <installroot> + <shortopt>R</shortopt> + <doc>root directory used when installing files (ala PHP's INSTALL_ROOT)</doc> + <arg>DIR</arg> + </installroot> + <ignore-errors> + <shortopt></shortopt> + <doc>force install even if there were errors</doc> + </ignore-errors> + <alldeps> + <shortopt>a</shortopt> + <doc>install all required and optional dependencies</doc> + </alldeps> + <onlyreqdeps> + <shortopt>o</shortopt> + <doc>install all required dependencies</doc> + </onlyreqdeps> + <offline> + <shortopt>O</shortopt> + <doc>do not attempt to download any urls or contact channels</doc> + </offline> + <pretend> + <shortopt>p</shortopt> + <doc>Only list the packages that would be downloaded</doc> + </pretend> + </options> + <doc><package> ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +</doc> + </upgrade> + <upgrade-all> + <summary>Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]</summary> + <function>doUpgradeAll</function> + <shortcut>ua</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>upgrade packages from a specific channel</doc> + <arg>CHAN</arg> + </channel> + <nodeps> + <shortopt>n</shortopt> + <doc>ignore dependencies, upgrade anyway</doc> + </nodeps> + <register-only> + <shortopt>r</shortopt> + <doc>do not install files, only register the package as upgraded</doc> + </register-only> + <nobuild> + <shortopt>B</shortopt> + <doc>don't build C extensions</doc> + </nobuild> + <nocompress> + <shortopt>Z</shortopt> + <doc>request uncompressed files when downloading</doc> + </nocompress> + <installroot> + <shortopt>R</shortopt> + <doc>root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM</doc> + <arg>DIR</arg> + </installroot> + <ignore-errors> + <shortopt></shortopt> + <doc>force install even if there were errors</doc> + </ignore-errors> + <loose> + <shortopt></shortopt> + <doc>do not check for recommended dependency version</doc> + </loose> + </options> + <doc> +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +</doc> + </upgrade-all> + <uninstall> + <summary>Un-install Package</summary> + <function>doUninstall</function> + <shortcut>un</shortcut> + <options> + <nodeps> + <shortopt>n</shortopt> + <doc>ignore dependencies, uninstall anyway</doc> + </nodeps> + <register-only> + <shortopt>r</shortopt> + <doc>do not remove files, only register the packages as not installed</doc> + </register-only> + <installroot> + <shortopt>R</shortopt> + <doc>root directory used when installing files (ala PHP's INSTALL_ROOT)</doc> + <arg>DIR</arg> + </installroot> + <ignore-errors> + <shortopt></shortopt> + <doc>force install even if there were errors</doc> + </ignore-errors> + <offline> + <shortopt>O</shortopt> + <doc>do not attempt to uninstall remotely</doc> + </offline> + </options> + <doc>[channel/]<package> ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) +</doc> + </uninstall> + <bundle> + <summary>Unpacks a Pecl Package</summary> + <function>doBundle</function> + <shortcut>bun</shortcut> + <options> + <destination> + <shortopt>d</shortopt> + <doc>Optional destination directory for unpacking (defaults to current path or "ext" if exists)</doc> + <arg>DIR</arg> + </destination> + <force> + <shortopt>f</shortopt> + <doc>Force the unpacking even if there were errors in the package</doc> + </force> + </options> + <doc><package> +Unpacks a Pecl Package into the selected location. It will download the +package if needed. +</doc> + </bundle> + <run-scripts> + <summary>Run Post-Install Scripts bundled with a package</summary> + <function>doRunScripts</function> + <shortcut>rs</shortcut> + <options /> + <doc><package> +Run post-installation scripts in package <package>, if any exist. +</doc> + </run-scripts> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Mirror.php b/includes/pear/PEAR/Command/Mirror.php new file mode 100644 index 0000000..4f646b2 --- /dev/null +++ b/includes/pear/PEAR/Command/Mirror.php @@ -0,0 +1,139 @@ +<?php +/** + * PEAR_Command_Mirror (download-all command) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Alexander Merz <alexmerz@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.2.0 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for providing file mirrors + * + * @category pear + * @package PEAR + * @author Alexander Merz <alexmerz@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.2.0 + */ +class PEAR_Command_Mirror extends PEAR_Command_Common +{ + var $commands = array( + 'download-all' => array( + 'summary' => 'Downloads each available package from the default channel', + 'function' => 'doDownloadAll', + 'shortcut' => 'da', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + ), + 'doc' => ' +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded' + ), + ); + + /** + * PEAR_Command_Mirror constructor. + * + * @access public + * @param object PEAR_Frontend a reference to an frontend + * @param object PEAR_Config a reference to the configuration data + */ + function PEAR_Command_Mirror(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + /** + * For unit-testing + */ + function &factory($a) + { + $a = &PEAR_Command::factory($a, $this->config); + return $a; + } + + /** + * retrieves a list of avaible Packages from master server + * and downloads them + * + * @access public + * @param string $command the command + * @param array $options the command options before the command + * @param array $params the stuff after the command name + * @return bool true if succesful + * @throw PEAR_Error + */ + function doDownloadAll($command, $options, $params) + { + $savechannel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + $this->config->set('default_channel', $channel); + + $this->ui->outputData('Using Channel ' . $this->config->get('default_channel')); + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $remoteInfo = array_flip($rest->listPackages($base, $channel)); + } + + if (PEAR::isError($remoteInfo)) { + return $remoteInfo; + } + + $cmd = &$this->factory("download"); + if (PEAR::isError($cmd)) { + return $cmd; + } + + $this->ui->outputData('Using Preferred State of ' . + $this->config->get('preferred_state')); + $this->ui->outputData('Gathering release information, please wait...'); + + /** + * Error handling not necessary, because already done by + * the download command + */ + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $cmd->run('download', array('downloadonly' => true), array_keys($remoteInfo)); + PEAR::staticPopErrorHandling(); + $this->config->set('default_channel', $savechannel); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage()); + } + + return true; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Mirror.xml b/includes/pear/PEAR/Command/Mirror.xml new file mode 100644 index 0000000..fe8be9d --- /dev/null +++ b/includes/pear/PEAR/Command/Mirror.xml @@ -0,0 +1,18 @@ +<commands version="1.0"> + <download-all> + <summary>Downloads each available package from the default channel</summary> + <function>doDownloadAll</function> + <shortcut>da</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>specify a channel other than the default channel</doc> + <arg>CHAN</arg> + </channel> + </options> + <doc> +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded</doc> + </download-all> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Package.php b/includes/pear/PEAR/Command/Package.php new file mode 100644 index 0000000..44c647b --- /dev/null +++ b/includes/pear/PEAR/Command/Package.php @@ -0,0 +1,1124 @@ +<?php +/** + * PEAR_Command_Package (package, package-validate, cvsdiff, cvstag, package-dependencies, + * sign, makerpm, convert commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Martin Jansen <mj@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Martin Jansen <mj@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + +class PEAR_Command_Package extends PEAR_Command_Common +{ + var $commands = array( + 'package' => array( + 'summary' => 'Build Package', + 'function' => 'doPackage', + 'shortcut' => 'p', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" +' + ), + 'package-validate' => array( + 'summary' => 'Validate Package Consistency', + 'function' => 'doPackageValidate', + 'shortcut' => 'pv', + 'options' => array(), + 'doc' => ' +', + ), + 'cvsdiff' => array( + 'summary' => 'Run a "cvs diff" for all files in a package', + 'function' => 'doCvsDiff', + 'shortcut' => 'cd', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'reallyquiet' => array( + 'shortopt' => 'Q', + 'doc' => 'Be really quiet', + ), + 'date' => array( + 'shortopt' => 'D', + 'doc' => 'Diff against revision of DATE', + 'arg' => 'DATE', + ), + 'release' => array( + 'shortopt' => 'R', + 'doc' => 'Diff against tag for package release REL', + 'arg' => 'REL', + ), + 'revision' => array( + 'shortopt' => 'r', + 'doc' => 'Diff against revision REV', + 'arg' => 'REV', + ), + 'context' => array( + 'shortopt' => 'c', + 'doc' => 'Generate context diff', + ), + 'unified' => array( + 'shortopt' => 'u', + 'doc' => 'Generate unified diff', + ), + 'ignore-case' => array( + 'shortopt' => 'i', + 'doc' => 'Ignore case, consider upper- and lower-case letters equivalent', + ), + 'ignore-whitespace' => array( + 'shortopt' => 'b', + 'doc' => 'Ignore changes in amount of white space', + ), + 'ignore-blank-lines' => array( + 'shortopt' => 'B', + 'doc' => 'Ignore changes that insert or delete blank lines', + ), + 'brief' => array( + 'doc' => 'Report only whether the files differ, no details', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => '<package.xml> +Compares all the files in a package. Without any options, this +command will compare the current code with the last checked-in code. +Using the -r or -R option you may compare the current code with that +of a specific release. +', + ), + 'svntag' => array( + 'summary' => 'Set SVN Release Tag', + 'function' => 'doSvnTag', + 'shortcut' => 'sv', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'slide' => array( + 'shortopt' => 'F', + 'doc' => 'Move (slide) tag if it exists', + ), + 'delete' => array( + 'shortopt' => 'd', + 'doc' => 'Remove tag', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => '<package.xml> [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running svntag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + ', + ), + 'cvstag' => array( + 'summary' => 'Set CVS Release Tag', + 'function' => 'doCvsTag', + 'shortcut' => 'ct', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'reallyquiet' => array( + 'shortopt' => 'Q', + 'doc' => 'Be really quiet', + ), + 'slide' => array( + 'shortopt' => 'F', + 'doc' => 'Move (slide) tag if it exists', + ), + 'delete' => array( + 'shortopt' => 'd', + 'doc' => 'Remove tag', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => '<package.xml> [files...] +Sets a CVS tag on all files in a package. Use this command after you have +packaged a distribution tarball with the "package" command to tag what +revisions of what files were in that release. If need to fix something +after running cvstag once, but before the tarball is released to the public, +use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. +', + ), + 'package-dependencies' => array( + 'summary' => 'Show package dependencies', + 'function' => 'doPackageDependencies', + 'shortcut' => 'pd', + 'options' => array(), + 'doc' => '<package-file> or <package.xml> or <install-package-name> +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.' + ), + 'sign' => array( + 'summary' => 'Sign a package distribution file', + 'function' => 'doSign', + 'shortcut' => 'si', + 'options' => array( + 'verbose' => array( + 'shortopt' => 'v', + 'doc' => 'Display GnuPG output', + ), + ), + 'doc' => '<package-file> +Signs a package distribution (.tar or .tgz) file with GnuPG.', + ), + 'makerpm' => array( + 'summary' => 'Builds an RPM spec file from a PEAR package', + 'function' => 'doMakeRPM', + 'shortcut' => 'rpm', + 'options' => array( + 'spec-template' => array( + 'shortopt' => 't', + 'arg' => 'FILE', + 'doc' => 'Use FILE as RPM spec file template' + ), + 'rpm-pkgname' => array( + 'shortopt' => 'p', + 'arg' => 'FORMAT', + 'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced +by the PEAR package name, defaults to "PEAR::%s".', + ), + ), + 'doc' => '<package-file> + +Creates an RPM .spec file for wrapping a PEAR package inside an RPM +package. Intended to be used from the SPECS directory, with the PEAR +package tarball in the SOURCES directory: + +$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz +Wrote RPM spec file PEAR::Net_Geo-1.0.spec +$ rpm -bb PEAR::Net_Socket-1.0.spec +... +Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm +', + ), + 'convert' => array( + 'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format', + 'function' => 'doConvert', + 'shortcut' => 'c2', + 'options' => array( + 'flat' => array( + 'shortopt' => 'f', + 'doc' => 'do not beautify the filelist.', + ), + ), + 'doc' => '[descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. +' + ), + ); + + var $output; + + /** + * PEAR_Command_Package constructor. + * + * @access public + */ + function PEAR_Command_Package(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _displayValidationResults($err, $warn, $strict = false) + { + foreach ($err as $e) { + $this->output .= "Error: $e\n"; + } + foreach ($warn as $w) { + $this->output .= "Warning: $w\n"; + } + $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n", + sizeof($err), sizeof($warn)); + if ($strict && count($err) > 0) { + $this->output .= "Fix these errors and try again."; + return false; + } + return true; + } + + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + $a = &new PEAR_Packager; + return $a; + } + + function &getPackageFile($config, $debug = false) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + $a = &new PEAR_PackageFile($config, $debug); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } + + function doPackage($command, $options, $params) + { + $this->output = ''; + $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml'; + $pkg2 = isset($params[1]) ? $params[1] : null; + if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) { + $pkg2 = 'package2.xml'; + } + + $packager = &$this->getPackager(); + $compress = empty($options['nocompress']) ? true : false; + $result = $packager->package($pkginfofile, $compress, $pkg2); + if (PEAR::isError($result)) { + return $this->raiseError($result); + } + + // Don't want output, only the package file name just created + if (isset($options['showname'])) { + $this->output = $result; + } + + if ($this->output) { + $this->ui->outputData($this->output, $command); + } + + return true; + } + + function doPackageValidate($command, $options, $params) + { + $this->output = ''; + if (count($params) < 1) { + $params[0] = 'package.xml'; + } + + $obj = &$this->getPackageFile($this->config, $this->_debug); + $obj->rawReturn(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $archive = $info->getArchiveFile(); + $tar = &new Archive_Tar($archive); + $tar->extract(dirname($info->getPackageFile())); + $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR . + $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR . + basename($info->getPackageFile())); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $valid = false; + if ($info->getPackagexmlVersion() == '2.0') { + if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) { + $info->flattenFileList(); + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + } else { + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + + $err = $warn = array(); + if ($errors = $info->getValidationWarnings()) { + foreach ($errors as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + $this->_displayValidationResults($err, $warn); + $this->ui->outputData($this->output, $command); + return true; + } + + function doSvnTag($command, $options, $params) + { + $this->output = ''; + $_cmd = $command; + if (count($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $packageFile = realpath($params[0]); + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('SVN tag failed'); + } + + $version = $info->getVersion(); + $package = $info->getName(); + $svntag = "$package-$version"; + + if (isset($options['delete'])) { + return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + $path = $this->_svnFindPath($packageFile); + + // Check if there are any modified files + $fp = popen('svn st --xml ' . dirname($packageFile), "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (!isset($options['quiet']) && strpos($out, 'item="modified"')) { + $params = array(array( + 'name' => 'modified', + 'type' => 'yesno', + 'default' => 'no', + 'prompt' => 'You have files in your SVN checkout (' . $path['from'] . ') that have been modified but not commited, do you still want to tag ' . $version . '?', + )); + $answers = $this->ui->confirmDialog($params); + + if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) { + return true; + } + } + + if (isset($options['slide'])) { + $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + // Check if tag already exists + $releaseTag = $path['local']['base'] . 'tags' . DIRECTORY_SEPARATOR . $svntag; + $existsCommand = 'svn ls ' . $path['base'] . 'tags/'; + + $fp = popen($existsCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (in_array($svntag . DIRECTORY_SEPARATOR, explode("\n", $out))) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.'); + } elseif (file_exists($path['local']['base'] . 'tags') === false) { + return $this->raiseError('Can not locate the tags directory at ' . $path['local']['base'] . 'tags'); + } elseif (is_writeable($path['local']['base'] . 'tags') === false) { + return $this->raiseError('Can not write to the tag directory at ' . $path['local']['base'] . 'tags'); + } else { + $makeCommand = 'svn mkdir ' . $releaseTag; + $this->output .= "+ $makeCommand\n"; + if (empty($options['dry-run'])) { + // We need to create the tag dir. + $fp = popen($makeCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + $this->output .= "$out\n"; + } + } + + $command = 'svn'; + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + $command .= ' copy --parents '; + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1); + $files = array_keys($info->getFilelist()); + if (!in_array(basename($packageFile), $files)) { + $files[] = basename($packageFile); + } + + array_shift($params); + if (count($params)) { + // add in additional files to be tagged (package files and such) + $files = array_merge($files, $params); + } + + $commands = array(); + foreach ($files as $file) { + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $commands[] = $command . ' ' . escapeshellarg($file) . ' ' . + escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file); + } + + $this->output .= implode("\n", $commands) . "\n"; + if (empty($options['dry-run'])) { + foreach ($commands as $command) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + } + + $command = 'svn ci -m "Tagging the ' . $version . ' release" ' . $releaseTag . "\n"; + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $_cmd); + return true; + } + + function _svnFindPath($file) + { + $xml = ''; + $command = "svn info --xml $file"; + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $xml .= rtrim($line)."\n"; + } + pclose($fp); + $url_tag = strpos($xml, '<url>'); + $url = substr($xml, $url_tag + 5, strpos($xml, '</url>', $url_tag + 5) - ($url_tag + 5)); + + $path = array(); + $path['from'] = substr($url, 0, strrpos($url, '/')); + $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1); + + // Figure out the local paths - see http://pear.php.net/bugs/17463 + $pos = strpos($file, DIRECTORY_SEPARATOR . 'trunk' . DIRECTORY_SEPARATOR); + if ($pos === false) { + $pos = strpos($file, DIRECTORY_SEPARATOR . 'branches' . DIRECTORY_SEPARATOR); + } + $path['local']['base'] = substr($file, 0, $pos + 1); + + return $path; + } + + function _svnRemoveTag($version, $package, $tag, $packageFile, $options) + { + $command = 'svn'; + + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + $command .= ' remove'; + $command .= ' -m "Removing tag for the ' . $version . ' release."'; + + $path = $this->_svnFindPath($packageFile); + $command .= ' ' . $path['base'] . 'tags/' . $tag; + + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $command); + return true; + } + + function doCvsTag($command, $options, $params) + { + $this->output = ''; + $_cmd = $command; + if (count($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $packageFile = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS tag failed'); + } + + $version = $info->getVersion(); + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version); + $cvstag = "RELEASE_$cvsversion"; + $files = array_keys($info->getFilelist()); + $command = 'cvs'; + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + if (isset($options['reallyquiet'])) { + $command .= ' -Q'; + } + + $command .= ' tag'; + if (isset($options['slide'])) { + $command .= ' -F'; + } + + if (isset($options['delete'])) { + $command .= ' -d'; + } + + $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]); + array_shift($params); + if (count($params)) { + // add in additional files to be tagged + $files = array_merge($files, $params); + } + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + foreach ($files as $file) { + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $command .= ' ' . escapeshellarg($file); + } + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $_cmd); + return true; + } + + function doCvsDiff($command, $options, $params) + { + $this->output = ''; + if (sizeof($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $file = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS diff failed'); + } + + $info1 = $info->getFilelist(); + $files = $info1; + $cmd = "cvs"; + if (isset($options['quiet'])) { + $cmd .= ' -q'; + unset($options['quiet']); + } + + if (isset($options['reallyquiet'])) { + $cmd .= ' -Q'; + unset($options['reallyquiet']); + } + + if (isset($options['release'])) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']); + $cvstag = "RELEASE_$cvsversion"; + $options['revision'] = $cvstag; + unset($options['release']); + } + + $execute = true; + if (isset($options['dry-run'])) { + $execute = false; + unset($options['dry-run']); + } + + $cmd .= ' diff'; + // the rest of the options are passed right on to "cvs diff" + foreach ($options as $option => $optarg) { + $arg = $short = false; + if (isset($this->commands[$command]['options'][$option])) { + $arg = $this->commands[$command]['options'][$option]['arg']; + $short = $this->commands[$command]['options'][$option]['shortopt']; + } + $cmd .= $short ? " -$short" : " --$option"; + if ($arg && $optarg) { + $cmd .= ($short ? '' : '=') . escapeshellarg($optarg); + } + } + + foreach ($files as $file) { + $cmd .= ' ' . escapeshellarg($file['name']); + } + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $cmd\n"; + } + + if ($execute) { + $fp = popen($cmd, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $command); + return true; + } + + function doPackageDependencies($command, $options, $params) + { + // $params[0] -> the PEAR package to list its information + if (count($params) !== 1) { + return $this->raiseError("bad parameter(s), try \"help $command\""); + } + + $obj = &$this->getPackageFile($this->config, $this->_debug); + if (is_file($params[0]) || strpos($params[0], '.xml') > 0) { + $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $reg = $this->config->getRegistry(); + $info = $obj->fromArray($reg->packageInfo($params[0])); + } + + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $deps = $info->getDeps(); + if (is_array($deps)) { + if ($info->getPackagexmlVersion() == '1.0') { + $data = array( + 'caption' => 'Dependencies for pear/' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", "Relation", "Version"), + ); + + foreach ($deps as $d) { + if (isset($d['optional'])) { + if ($d['optional'] == 'yes') { + $req = 'No'; + } else { + $req = 'Yes'; + } + } else { + $req = 'Yes'; + } + + if (isset($this->_deps_rel_trans[$d['rel']])) { + $rel = $this->_deps_rel_trans[$d['rel']]; + } else { + $rel = $d['rel']; + } + + if (isset($this->_deps_type_trans[$d['type']])) { + $type = ucfirst($this->_deps_type_trans[$d['type']]); + } else { + $type = $d['type']; + } + + if (isset($d['name'])) { + $name = $d['name']; + } else { + $name = ''; + } + + if (isset($d['version'])) { + $version = $d['version']; + } else { + $version = ''; + } + + $data['data'][] = array($req, $type, $name, $rel, $version); + } + } else { // package.xml 2.0 dependencies display + require_once 'PEAR/Dependency2.php'; + $deps = $info->getDependencies(); + $reg = &$this->config->getRegistry(); + if (is_array($deps)) { + $d = new PEAR_Dependency2($this->config, array(), ''); + $data = array( + 'caption' => 'Dependencies for ' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'), + ); + foreach ($deps as $type => $subd) { + $req = ($type == 'required') ? 'Yes' : 'No'; + if ($type == 'group') { + $group = $subd['attribs']['name']; + } else { + $group = ''; + } + + if (!isset($subd[0])) { + $subd = array($subd); + } + + foreach ($subd as $groupa) { + foreach ($groupa as $deptype => $depinfo) { + if ($deptype == 'attribs') { + continue; + } + + if ($deptype == 'pearinstaller') { + $deptype = 'pear Installer'; + } + + if (!isset($depinfo[0])) { + $depinfo = array($depinfo); + } + + foreach ($depinfo as $inf) { + $name = ''; + if (isset($inf['channel'])) { + $alias = $reg->channelAlias($inf['channel']); + if (!$alias) { + $alias = '(channel?) ' .$inf['channel']; + } + $name = $alias . '/'; + + } + if (isset($inf['name'])) { + $name .= $inf['name']; + } elseif (isset($inf['pattern'])) { + $name .= $inf['pattern']; + } else { + $name .= ''; + } + + if (isset($inf['uri'])) { + $name .= ' [' . $inf['uri'] . ']'; + } + + if (isset($inf['conflicts'])) { + $ver = 'conflicts'; + } else { + $ver = $d->_getExtraString($inf); + } + + $data['data'][] = array($req, ucfirst($deptype), $name, + $ver, $group); + } + } + } + } + } + } + + $this->ui->outputData($data, $command); + return true; + } + + // Fallback + $this->ui->outputData("This package does not have any dependencies.", $command); + } + + function doSign($command, $options, $params) + { + // should move most of this code into PEAR_Packager + // so it'll be easy to implement "pear package --sign" + if (count($params) !== 1) { + return $this->raiseError("bad parameter(s), try \"help $command\""); + } + + require_once 'System.php'; + require_once 'Archive/Tar.php'; + + if (!file_exists($params[0])) { + return $this->raiseError("file does not exist: $params[0]"); + } + + $obj = $this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $tar = new Archive_Tar($params[0]); + + $tmpdir = $this->config->get('temp_dir'); + $tmpdir = System::mktemp(' -t "' . $tmpdir . '" -d pearsign'); + if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) { + return $this->raiseError("failed to extract tar file"); + } + + if (file_exists("$tmpdir/package.sig")) { + return $this->raiseError("package already signed"); + } + + $packagexml = 'package.xml'; + if (file_exists("$tmpdir/package2.xml")) { + $packagexml = 'package2.xml'; + } + + if (file_exists("$tmpdir/package.sig")) { + unlink("$tmpdir/package.sig"); + } + + if (!file_exists("$tmpdir/$packagexml")) { + return $this->raiseError("Extracted file $tmpdir/$packagexml not found."); + } + + $input = $this->ui->userDialog($command, + array('GnuPG Passphrase'), + array('password')); + if (!isset($input[0])) { + //use empty passphrase + $input[0] = ''; + } + + $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null'; + $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w"); + if (!$gpg) { + return $this->raiseError("gpg command failed"); + } + + fwrite($gpg, "$input[0]\n"); + if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) { + return $this->raiseError("gpg sign failed"); + } + + if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) { + return $this->raiseError('failed adding signature to file'); + } + + $this->ui->outputData("Package signed.", $command); + return true; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + /** + * For unit testing purposes + */ + function &getCommandPackaging(&$ui, &$config) + { + if (!class_exists('PEAR_Command_Packaging')) { + if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) { + fclose($fp); + include_once 'PEAR/Command/Packaging.php'; + } + } + + if (class_exists('PEAR_Command_Packaging')) { + $a = &new PEAR_Command_Packaging($ui, $config); + } else { + $a = null; + } + + return $a; + } + + function doMakeRPM($command, $options, $params) + { + + // Check to see if PEAR_Command_Packaging is installed, and + // transparently switch to use the "make-rpm-spec" command from it + // instead, if it does. Otherwise, continue to use the old version + // of "makerpm" supplied with this package (PEAR). + $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config); + if ($packaging_cmd !== null) { + $this->ui->outputData('PEAR_Command_Packaging is installed; using '. + 'newer "make-rpm-spec" command instead'); + return $packaging_cmd->run('make-rpm-spec', $options, $params); + } + + $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '. + 'improved version is available via "pear make-rpm-spec", which '. + 'is available by installing PEAR_Command_Packaging'); + return true; + } + + function doConvert($command, $options, $params) + { + $packagexml = isset($params[0]) ? $params[0] : 'package.xml'; + $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) . + DIRECTORY_SEPARATOR . 'package2.xml'; + $pkg = &$this->getPackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + return $this->raiseError($pf); + } + + if (is_a($pf, 'PEAR_PackageFile_v2')) { + $this->ui->outputData($packagexml . ' is already a package.xml version 2.0'); + return true; + } + + $gen = &$pf->getDefaultGenerator(); + $newpf = &$gen->toV2(); + $newpf->setPackagefile($newpackagexml); + $gen = &$newpf->getDefaultGenerator(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL); + $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($saved)) { + if (is_array($saved->getUserInfo())) { + foreach ($saved->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + + $this->ui->outputData($saved->getMessage()); + return true; + } + + $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"'); + return true; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Package.xml b/includes/pear/PEAR/Command/Package.xml new file mode 100644 index 0000000..d1aff9d --- /dev/null +++ b/includes/pear/PEAR/Command/Package.xml @@ -0,0 +1,237 @@ +<commands version="1.0"> + <package> + <summary>Build Package</summary> + <function>doPackage</function> + <shortcut>p</shortcut> + <options> + <nocompress> + <shortopt>Z</shortopt> + <doc>Do not gzip the package file</doc> + </nocompress> + <showname> + <shortopt>n</shortopt> + <doc>Print the name of the packaged file.</doc> + </showname> + </options> + <doc>[descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" +</doc> + </package> + <package-validate> + <summary>Validate Package Consistency</summary> + <function>doPackageValidate</function> + <shortcut>pv</shortcut> + <options /> + <doc> +</doc> + </package-validate> + <cvsdiff> + <summary>Run a "cvs diff" for all files in a package</summary> + <function>doCvsDiff</function> + <shortcut>cd</shortcut> + <options> + <quiet> + <shortopt>q</shortopt> + <doc>Be quiet</doc> + </quiet> + <reallyquiet> + <shortopt>Q</shortopt> + <doc>Be really quiet</doc> + </reallyquiet> + <date> + <shortopt>D</shortopt> + <doc>Diff against revision of DATE</doc> + <arg>DATE</arg> + </date> + <release> + <shortopt>R</shortopt> + <doc>Diff against tag for package release REL</doc> + <arg>REL</arg> + </release> + <revision> + <shortopt>r</shortopt> + <doc>Diff against revision REV</doc> + <arg>REV</arg> + </revision> + <context> + <shortopt>c</shortopt> + <doc>Generate context diff</doc> + </context> + <unified> + <shortopt>u</shortopt> + <doc>Generate unified diff</doc> + </unified> + <ignore-case> + <shortopt>i</shortopt> + <doc>Ignore case, consider upper- and lower-case letters equivalent</doc> + </ignore-case> + <ignore-whitespace> + <shortopt>b</shortopt> + <doc>Ignore changes in amount of white space</doc> + </ignore-whitespace> + <ignore-blank-lines> + <shortopt>B</shortopt> + <doc>Ignore changes that insert or delete blank lines</doc> + </ignore-blank-lines> + <brief> + <shortopt></shortopt> + <doc>Report only whether the files differ, no details</doc> + </brief> + <dry-run> + <shortopt>n</shortopt> + <doc>Don't do anything, just pretend</doc> + </dry-run> + </options> + <doc><package.xml> +Compares all the files in a package. Without any options, this +command will compare the current code with the last checked-in code. +Using the -r or -R option you may compare the current code with that +of a specific release. +</doc> + </cvsdiff> + <svntag> + <summary>Set SVN Release Tag</summary> + <function>doSvnTag</function> + <shortcut>sv</shortcut> + <options> + <quiet> + <shortopt>q</shortopt> + <doc>Be quiet</doc> + </quiet> + <slide> + <shortopt>F</shortopt> + <doc>Move (slide) tag if it exists</doc> + </slide> + <delete> + <shortopt>d</shortopt> + <doc>Remove tag</doc> + </delete> + <dry-run> + <shortopt>n</shortopt> + <doc>Don't do anything, just pretend</doc> + </dry-run> + </options> + <doc><package.xml> [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running svntag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + </doc> + </svntag> + <cvstag> + <summary>Set CVS Release Tag</summary> + <function>doCvsTag</function> + <shortcut>ct</shortcut> + <options> + <quiet> + <shortopt>q</shortopt> + <doc>Be quiet</doc> + </quiet> + <reallyquiet> + <shortopt>Q</shortopt> + <doc>Be really quiet</doc> + </reallyquiet> + <slide> + <shortopt>F</shortopt> + <doc>Move (slide) tag if it exists</doc> + </slide> + <delete> + <shortopt>d</shortopt> + <doc>Remove tag</doc> + </delete> + <dry-run> + <shortopt>n</shortopt> + <doc>Don't do anything, just pretend</doc> + </dry-run> + </options> + <doc><package.xml> [files...] +Sets a CVS tag on all files in a package. Use this command after you have +packaged a distribution tarball with the "package" command to tag what +revisions of what files were in that release. If need to fix something +after running cvstag once, but before the tarball is released to the public, +use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. +</doc> + </cvstag> + <package-dependencies> + <summary>Show package dependencies</summary> + <function>doPackageDependencies</function> + <shortcut>pd</shortcut> + <options /> + <doc><package-file> or <package.xml> or <install-package-name> +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.</doc> + </package-dependencies> + <sign> + <summary>Sign a package distribution file</summary> + <function>doSign</function> + <shortcut>si</shortcut> + <options> + <verbose> + <shortopt>v</shortopt> + <doc>Display GnuPG output</doc> + </verbose> + </options> + <doc><package-file> +Signs a package distribution (.tar or .tgz) file with GnuPG.</doc> + </sign> + <makerpm> + <summary>Builds an RPM spec file from a PEAR package</summary> + <function>doMakeRPM</function> + <shortcut>rpm</shortcut> + <options> + <spec-template> + <shortopt>t</shortopt> + <doc>Use FILE as RPM spec file template</doc> + <arg>FILE</arg> + </spec-template> + <rpm-pkgname> + <shortopt>p</shortopt> + <doc>Use FORMAT as format string for RPM package name, %s is replaced +by the PEAR package name, defaults to "PEAR::%s".</doc> + <arg>FORMAT</arg> + </rpm-pkgname> + </options> + <doc><package-file> + +Creates an RPM .spec file for wrapping a PEAR package inside an RPM +package. Intended to be used from the SPECS directory, with the PEAR +package tarball in the SOURCES directory: + +$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz +Wrote RPM spec file PEAR::Net_Geo-1.0.spec +$ rpm -bb PEAR::Net_Socket-1.0.spec +... +Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm +</doc> + </makerpm> + <convert> + <summary>Convert a package.xml 1.0 to package.xml 2.0 format</summary> + <function>doConvert</function> + <shortcut>c2</shortcut> + <options> + <flat> + <shortopt>f</shortopt> + <doc>do not beautify the filelist.</doc> + </flat> + </options> + <doc>[descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. +</doc> + </convert> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Pickle.php b/includes/pear/PEAR/Command/Pickle.php new file mode 100644 index 0000000..7fe8da7 --- /dev/null +++ b/includes/pear/PEAR/Command/Pickle.php @@ -0,0 +1,421 @@ +<?php +/** + * PEAR_Command_Pickle (pickle command) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.1 + */ + +class PEAR_Command_Pickle extends PEAR_Command_Common +{ + var $commands = array( + 'pickle' => array( + 'summary' => 'Build PECL Package', + 'function' => 'doPackage', + 'shortcut' => 'pi', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. +' + ), + ); + + /** + * PEAR_Command_Package constructor. + * + * @access public + */ + function PEAR_Command_Pickle(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + /** + * For unit-testing ease + * + * @return PEAR_Packager + */ + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + + $a = &new PEAR_Packager; + return $a; + } + + /** + * For unit-testing ease + * + * @param PEAR_Config $config + * @param bool $debug + * @param string|null $tmpdir + * @return PEAR_PackageFile + */ + function &getPackageFile($config, $debug = false) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $a = &new PEAR_PackageFile($config, $debug); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } + + function doPackage($command, $options, $params) + { + $this->output = ''; + $pkginfofile = isset($params[0]) ? $params[0] : 'package2.xml'; + $packager = &$this->getPackager(); + if (PEAR::isError($err = $this->_convertPackage($pkginfofile))) { + return $err; + } + + $compress = empty($options['nocompress']) ? true : false; + $result = $packager->package($pkginfofile, $compress, 'package.xml'); + if (PEAR::isError($result)) { + return $this->raiseError($result); + } + + // Don't want output, only the package file name just created + if (isset($options['showname'])) { + $this->ui->outputData($result, $command); + } + + return true; + } + + function _convertPackage($packagexml) + { + $pkg = &$this->getPackageFile($this->config); + $pf2 = &$pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + if (!is_a($pf2, 'PEAR_PackageFile_v2')) { + return $this->raiseError('Cannot process "' . + $packagexml . '", is not a package.xml 2.0'); + } + + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->config); + if ($pf2->getPackageType() != 'extsrc' && $pf2->getPackageType() != 'zendextsrc') { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", is not an extension source package. Using a PEAR_PackageFileManager-based ' . + 'script is an option'); + } + + if (is_array($pf2->getUsesRole())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + if (is_array($pf2->getUsesTask())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom tasks. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + $deps = $pf2->getDependencies(); + if (isset($deps['group'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains dependency groups. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($deps['required']['subpackage']) || + isset($deps['optional']['subpackage'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains subpackage dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['os'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains os dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['arch'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains arch dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + $pf->setPackage($pf2->getPackage()); + $pf->setSummary($pf2->getSummary()); + $pf->setDescription($pf2->getDescription()); + foreach ($pf2->getMaintainers() as $maintainer) { + $pf->addMaintainer($maintainer['role'], $maintainer['handle'], + $maintainer['name'], $maintainer['email']); + } + + $pf->setVersion($pf2->getVersion()); + $pf->setDate($pf2->getDate()); + $pf->setLicense($pf2->getLicense()); + $pf->setState($pf2->getState()); + $pf->setNotes($pf2->getNotes()); + $pf->addPhpDep($deps['required']['php']['min'], 'ge'); + if (isset($deps['required']['php']['max'])) { + $pf->addPhpDep($deps['required']['php']['max'], 'le'); + } + + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['required']['extension'])) { + if (!isset($deps['required']['extension'][0])) { + $deps['required']['extension'] = array($deps['required']['extension']); + } + + foreach ($deps['required']['extension'] as $dep) { + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + if (isset($deps['optional']['extension'])) { + if (!isset($deps['optional']['extension'][0])) { + $deps['optional']['extension'] = array($deps['optional']['extension']); + } + + foreach ($deps['optional']['extension'] as $dep) { + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + $contents = $pf2->getContents(); + $release = $pf2->getReleases(); + if (isset($releases[0])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'multiple extsrcrelease/zendextsrcrelease tags. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if ($configoptions = $pf2->getConfigureOptions()) { + foreach ($configoptions as $option) { + $default = isset($option['default']) ? $option['default'] : false; + $pf->addConfigureOption($option['name'], $option['prompt'], $default); + } + } + + if (isset($release['filelist']['ignore'])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'ignore tags. Using a PEAR_PackageFileManager-based script or the convert' . + ' command is an option'); + } + + if (isset($release['filelist']['install']) && + !isset($release['filelist']['install'][0])) { + $release['filelist']['install'] = array($release['filelist']['install']); + } + + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $baseinstalldir = $contents['dir']['attribs']['baseinstalldir']; + } else { + $baseinstalldir = false; + } + + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + + foreach ($contents['dir']['file'] as $file) { + if ($baseinstalldir && !isset($file['attribs']['baseinstalldir'])) { + $file['attribs']['baseinstalldir'] = $baseinstalldir; + } + + $processFile = $file; + unset($processFile['attribs']); + if (count($processFile)) { + foreach ($processFile as $name => $task) { + if ($name != $pf2->getTasksNs() . ':replace') { + return $this->raiseError('Cannot safely process "' . $packagexml . + '" contains tasks other than replace. Using a ' . + 'PEAR_PackageFileManager-based script is an option.'); + } + $file['attribs']['replace'][] = $task; + } + } + + if (!in_array($file['attribs']['role'], PEAR_Common::getFileRoles())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($release['filelist']['install'])) { + foreach ($release['filelist']['install'] as $installas) { + if ($installas['attribs']['name'] == $file['attribs']['name']) { + $file['attribs']['install-as'] = $installas['attribs']['as']; + } + } + } + + $pf->addFile('/', $file['attribs']['name'], $file['attribs']); + } + + if ($pf2->getChangeLog()) { + $this->ui->outputData('WARNING: changelog is not translated to package.xml ' . + '1.0, use PEAR_PackageFileManager-based script if you need changelog-' . + 'translation for package.xml 1.0'); + } + + $gen = &$pf->getDefaultGenerator(); + $gen->toPackageFile('.'); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Pickle.xml b/includes/pear/PEAR/Command/Pickle.xml new file mode 100644 index 0000000..721ecea --- /dev/null +++ b/includes/pear/PEAR/Command/Pickle.xml @@ -0,0 +1,36 @@ +<commands version="1.0"> + <pickle> + <summary>Build PECL Package</summary> + <function>doPackage</function> + <shortcut>pi</shortcut> + <options> + <nocompress> + <shortopt>Z</shortopt> + <doc>Do not gzip the package file</doc> + </nocompress> + <showname> + <shortopt>n</shortopt> + <doc>Print the name of the packaged file.</doc> + </showname> + </options> + <doc>[descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. +</doc> + </pickle> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Registry.php b/includes/pear/PEAR/Command/Registry.php new file mode 100644 index 0000000..4110123 --- /dev/null +++ b/includes/pear/PEAR/Command/Registry.php @@ -0,0 +1,1145 @@ +<?php +/** + * PEAR_Command_Registry (list, list-files, shell-test, info commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for registry manipulation + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Registry extends PEAR_Command_Common +{ + var $commands = array( + 'list' => array( + 'summary' => 'List Installed Packages In The Default Channel', + 'function' => 'doList', + 'shortcut' => 'l', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'list installed packages from this channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'list installed packages from all channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '<package> +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. +', + ), + 'list-files' => array( + 'summary' => 'List Files In Installed Package', + 'function' => 'doFileList', + 'shortcut' => 'fl', + 'options' => array(), + 'doc' => '<package> +List the files in an installed package. +' + ), + 'shell-test' => array( + 'summary' => 'Shell Script Test', + 'function' => 'doShellTest', + 'shortcut' => 'st', + 'options' => array(), + 'doc' => '<package> [[relation] version] +Tests if a package is installed in the system. Will exit(1) if it is not. + <relation> The version comparison operator. One of: + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + <version> The version to compare with +'), + 'info' => array( + 'summary' => 'Display information about a package', + 'function' => 'doInfo', + 'shortcut' => 'in', + 'options' => array(), + 'doc' => '<package> +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package.' + ) + ); + + /** + * PEAR_Command_Registry constructor. + * + * @access public + */ + function PEAR_Command_Registry(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _sortinfo($a, $b) + { + $apackage = isset($a['package']) ? $a['package'] : $a['name']; + $bpackage = isset($b['package']) ? $b['package'] : $b['name']; + return strcmp($apackage, $bpackage); + } + + function doList($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $channelinfo = isset($options['channelinfo']); + if (isset($options['allchannels']) && !$channelinfo) { + return $this->doListAll($command, array(), $params); + } + + if (isset($options['allchannels']) && $channelinfo) { + // allchannels with $channelinfo + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + $options['channel'] = $channel->getName(); + $ret = $this->doList($command, $options, $params); + + if (PEAR::isError($ret)) { + $errors[] = $ret; + } + } + + PEAR::staticPopErrorHandling(); + if (count($errors)) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + if (count($params) === 1) { + return $this->doFileList($command, $options, $params); + } + + if (isset($options['channel'])) { + if (!$reg->channelExists($options['channel'])) { + return $this->raiseError('Channel "' . $options['channel'] .'" does not exist'); + } + + $channel = $reg->channelName($options['channel']); + } else { + $channel = $this->config->get('default_channel'); + } + + $installed = $reg->packageInfo(null, null, $channel); + usort($installed, array(&$this, '_sortinfo')); + + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel, + ); + if ($channelinfo) { + $data['headline'] = array('Channel', 'Package', 'Version', 'State'); + } + + if (count($installed) && !isset($data['data'])) { + $data['data'] = array(); + } + + foreach ($installed as $package) { + $pobj = $reg->getPackage(isset($package['package']) ? + $package['package'] : $package['name'], $channel); + if ($channelinfo) { + $packageinfo = array($pobj->getChannel(), $pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } else { + $packageinfo = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + $data['data'][] = $packageinfo; + } + + if (count($installed) === 0) { + if (!$channelinfo) { + $data = '(no packages installed from channel ' . $channel . ')'; + } else { + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'channel' => $channel, + 'data' => array(array('(no packages installed)')), + ); + } + } + + $this->ui->outputData($data, $command); + return true; + } + + function doListAll($command, $options, $params) + { + // This duplicate code is deprecated over + // list --channelinfo, which gives identical + // output for list and list --allchannels. + $reg = &$this->config->getRegistry(); + $installed = $reg->packageInfo(null, null, null); + foreach ($installed as $channel => $packages) { + usort($packages, array($this, '_sortinfo')); + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel + ); + + foreach ($packages as $package) { + $p = isset($package['package']) ? $package['package'] : $package['name']; + $pobj = $reg->getPackage($p, $channel); + $data['data'][] = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + + // Adds a blank line after each section + $data['data'][] = array(); + + if (count($packages) === 0) { + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'data' => array(array('(no packages installed)'), array()), + 'channel' => $channel + ); + } + $this->ui->outputData($data, $command); + } + return true; + } + + function doFileList($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('list-files expects 1 parameter'); + } + + $reg = &$this->config->getRegistry(); + $fp = false; + if (!is_dir($params[0]) && (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + $headings = array('Package File', 'Install Path'); + $installed = false; + } else { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $info = &$reg->getPackage($parsed['package'], $parsed['channel']); + $headings = array('Type', 'Install Path'); + $installed = true; + } + + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + if ($info === null) { + return $this->raiseError("`$params[0]' not installed"); + } + + $list = ($info->getPackagexmlVersion() == '1.0' || $installed) ? + $info->getFilelist() : $info->getContents(); + if ($installed) { + $caption = 'Installed Files For ' . $params[0]; + } else { + $caption = 'Contents of ' . basename($params[0]); + } + + $data = array( + 'caption' => $caption, + 'border' => true, + 'headline' => $headings); + if ($info->getPackagexmlVersion() == '1.0' || $installed) { + foreach ($list as $file => $att) { + if ($installed) { + if (empty($att['installed_as'])) { + continue; + } + $data['data'][] = array($att['role'], $att['installed_as']); + } else { + if (isset($att['baseinstalldir']) && !in_array($att['role'], + array('test', 'data', 'doc'))) { + $dest = $att['baseinstalldir'] . DIRECTORY_SEPARATOR . + $file; + } else { + $dest = $file; + } + switch ($att['role']) { + case 'test': + case 'data': + case 'doc': + $role = $att['role']; + if ($role == 'test') { + $role .= 's'; + } + $dest = $this->config->get($role . '_dir') . DIRECTORY_SEPARATOR . + $info->getPackage() . DIRECTORY_SEPARATOR . $dest; + break; + case 'php': + default: + $dest = $this->config->get('php_dir') . DIRECTORY_SEPARATOR . + $dest; + } + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $dest = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + $dest); + $file = preg_replace('!/+!', '/', $file); + $data['data'][] = array($file, $dest); + } + } + } else { // package.xml 2.0, not installed + if (!isset($list['dir']['file'][0])) { + $list['dir']['file'] = array($list['dir']['file']); + } + + foreach ($list['dir']['file'] as $att) { + $att = $att['attribs']; + $file = $att['name']; + $role = &PEAR_Installer_Role::factory($info, $att['role'], $this->config); + $role->setup($this, $info, $att, $file); + if (!$role->isInstallable()) { + $dest = '(not installable)'; + } else { + $dest = $role->processInstallation($info, $att, $file, ''); + if (PEAR::isError($dest)) { + $dest = '(Unknown role "' . $att['role'] . ')'; + } else { + list(,, $dest) = $dest; + } + } + $data['data'][] = array($file, $dest); + } + } + + $this->ui->outputData($data, $command); + return true; + } + + function doShellTest($command, $options, $params) + { + if (count($params) < 1) { + return PEAR::raiseError('ERROR, usage: pear shell-test packagename [[relation] version]'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $reg = &$this->config->getRegistry(); + $info = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($info)) { + exit(1); // invalid package name + } + + $package = $info['package']; + $channel = $info['channel']; + // "pear shell-test Foo" + if (!$reg->packageExists($package, $channel)) { + if ($channel == 'pecl.php.net') { + if ($reg->packageExists($package, 'pear.php.net')) { + $channel = 'pear.php.net'; // magically change channels for extensions + } + } + } + + if (count($params) === 1) { + if (!$reg->packageExists($package, $channel)) { + exit(1); + } + // "pear shell-test Foo 1.0" + } elseif (count($params) === 2) { + $v = $reg->packageInfo($package, 'version', $channel); + if (!$v || !version_compare("$v", "{$params[1]}", "ge")) { + exit(1); + } + // "pear shell-test Foo ge 1.0" + } elseif (count($params) === 3) { + $v = $reg->packageInfo($package, 'version', $channel); + if (!$v || !version_compare("$v", "{$params[2]}", $params[1])) { + exit(1); + } + } else { + PEAR::staticPopErrorHandling(); + $this->raiseError("$command: expects 1 to 3 parameters"); + exit(1); + } + } + + function doInfo($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('pear info expects 1 parameter'); + } + + $info = $fp = false; + $reg = &$this->config->getRegistry(); + if (is_file($params[0]) && !is_dir($params[0]) && + (file_exists($params[0]) || $fp = @fopen($params[0], 'r')) + ) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $obj = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($obj)) { + $uinfo = $obj->getUserInfo(); + if (is_array($uinfo)) { + foreach ($uinfo as $message) { + if (is_array($message)) { + $message = $message['message']; + } + $this->ui->outputData($message); + } + } + + return $this->raiseError($obj); + } + + if ($obj->getPackagexmlVersion() != '1.0') { + return $this->_doInfo2($command, $options, $params, $obj, false); + } + + $info = $obj->toArray(); + } else { + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = $reg->packageInfo($package, null, $channel); + if (isset($info['old'])) { + $obj = $reg->getPackage($package, $channel); + return $this->_doInfo2($command, $options, $params, $obj, true); + } + } + + if (PEAR::isError($info)) { + return $info; + } + + if (empty($info)) { + $this->raiseError("No information found for `$params[0]'"); + return; + } + + unset($info['filelist']); + unset($info['dirtree']); + unset($info['changelog']); + if (isset($info['xsdversion'])) { + $info['package.xml version'] = $info['xsdversion']; + unset($info['xsdversion']); + } + + if (isset($info['packagerversion'])) { + $info['packaged with PEAR version'] = $info['packagerversion']; + unset($info['packagerversion']); + } + + $keys = array_keys($info); + $longtext = array('description', 'summary'); + foreach ($keys as $key) { + if (is_array($info[$key])) { + switch ($key) { + case 'maintainers': { + $i = 0; + $mstr = ''; + foreach ($info[$key] as $m) { + if ($i++ > 0) { + $mstr .= "\n"; + } + $mstr .= $m['name'] . " <"; + if (isset($m['email'])) { + $mstr .= $m['email']; + } else { + $mstr .= $m['handle'] . '@php.net'; + } + $mstr .= "> ($m[role])"; + } + $info[$key] = $mstr; + break; + } + case 'release_deps': { + $i = 0; + $dstr = ''; + foreach ($info[$key] as $d) { + if (isset($this->_deps_rel_trans[$d['rel']])) { + $rel = $this->_deps_rel_trans[$d['rel']]; + } else { + $rel = $d['rel']; + } + if (isset($this->_deps_type_trans[$d['type']])) { + $type = ucfirst($this->_deps_type_trans[$d['type']]); + } else { + $type = $d['type']; + } + if (isset($d['name'])) { + $name = $d['name'] . ' '; + } else { + $name = ''; + } + if (isset($d['version'])) { + $version = $d['version'] . ' '; + } else { + $version = ''; + } + if (isset($d['optional']) && $d['optional'] == 'yes') { + $optional = ' (optional)'; + } else { + $optional = ''; + } + $dstr .= "$type $name$rel $version$optional\n"; + } + $info[$key] = $dstr; + break; + } + case 'provides' : { + $debug = $this->config->get('verbose'); + if ($debug < 2) { + $pstr = 'Classes: '; + } else { + $pstr = ''; + } + $i = 0; + foreach ($info[$key] as $p) { + if ($debug < 2 && $p['type'] != "class") { + continue; + } + // Only print classes when verbosity mode is < 2 + if ($debug < 2) { + if ($i++ > 0) { + $pstr .= ", "; + } + $pstr .= $p['name']; + } else { + if ($i++ > 0) { + $pstr .= "\n"; + } + $pstr .= ucfirst($p['type']) . " " . $p['name']; + if (isset($p['explicit']) && $p['explicit'] == 1) { + $pstr .= " (explicit)"; + } + } + } + $info[$key] = $pstr; + break; + } + case 'configure_options' : { + foreach ($info[$key] as $i => $p) { + $info[$key][$i] = array_map(null, array_keys($p), array_values($p)); + $info[$key][$i] = array_map(create_function('$a', + 'return join(" = ",$a);'), $info[$key][$i]); + $info[$key][$i] = implode(', ', $info[$key][$i]); + } + $info[$key] = implode("\n", $info[$key]); + break; + } + default: { + $info[$key] = implode(", ", $info[$key]); + break; + } + } + } + + if ($key == '_lastmodified') { + $hdate = date('Y-m-d', $info[$key]); + unset($info[$key]); + $info['Last Modified'] = $hdate; + } elseif ($key == '_lastversion') { + $info['Previous Installed Version'] = $info[$key] ? $info[$key] : '- None -'; + unset($info[$key]); + } else { + $info[$key] = trim($info[$key]); + if (in_array($key, $longtext)) { + $info[$key] = preg_replace('/ +/', ' ', $info[$key]); + } + } + } + + $caption = 'About ' . $info['package'] . '-' . $info['version']; + $data = array( + 'caption' => $caption, + 'border' => true); + foreach ($info as $key => $value) { + $key = ucwords(trim(str_replace('_', ' ', $key))); + $data['data'][] = array($key, $value); + } + $data['raw'] = $info; + + $this->ui->outputData($data, 'package-info'); + } + + /** + * @access private + */ + function _doInfo2($command, $options, $params, &$obj, $installed) + { + $reg = &$this->config->getRegistry(); + $caption = 'About ' . $obj->getChannel() . '/' .$obj->getPackage() . '-' . + $obj->getVersion(); + $data = array( + 'caption' => $caption, + 'border' => true); + switch ($obj->getPackageType()) { + case 'php' : + $release = 'PEAR-style PHP-based Package'; + break; + case 'extsrc' : + $release = 'PECL-style PHP extension (source code)'; + break; + case 'zendextsrc' : + $release = 'PECL-style Zend extension (source code)'; + break; + case 'extbin' : + $release = 'PECL-style PHP extension (binary)'; + break; + case 'zendextbin' : + $release = 'PECL-style Zend extension (binary)'; + break; + case 'bundle' : + $release = 'Package bundle (collection of packages)'; + break; + } + $extends = $obj->getExtends(); + $extends = $extends ? + $obj->getPackage() . ' (extends ' . $extends . ')' : $obj->getPackage(); + if ($src = $obj->getSourcePackage()) { + $extends .= ' (source package ' . $src['channel'] . '/' . $src['package'] . ')'; + } + + $info = array( + 'Release Type' => $release, + 'Name' => $extends, + 'Channel' => $obj->getChannel(), + 'Summary' => preg_replace('/ +/', ' ', $obj->getSummary()), + 'Description' => preg_replace('/ +/', ' ', $obj->getDescription()), + ); + $info['Maintainers'] = ''; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + $leads = $obj->{"get{$role}s"}(); + if (!$leads) { + continue; + } + + if (isset($leads['active'])) { + $leads = array($leads); + } + + foreach ($leads as $lead) { + if (!empty($info['Maintainers'])) { + $info['Maintainers'] .= "\n"; + } + + $active = $lead['active'] == 'no' ? ', inactive' : ''; + $info['Maintainers'] .= $lead['name'] . ' <'; + $info['Maintainers'] .= $lead['email'] . "> ($role$active)"; + } + } + + $info['Release Date'] = $obj->getDate(); + if ($time = $obj->getTime()) { + $info['Release Date'] .= ' ' . $time; + } + + $info['Release Version'] = $obj->getVersion() . ' (' . $obj->getState() . ')'; + $info['API Version'] = $obj->getVersion('api') . ' (' . $obj->getState('api') . ')'; + $info['License'] = $obj->getLicense(); + $uri = $obj->getLicenseLocation(); + if ($uri) { + if (isset($uri['uri'])) { + $info['License'] .= ' (' . $uri['uri'] . ')'; + } else { + $extra = $obj->getInstalledLocation($info['filesource']); + if ($extra) { + $info['License'] .= ' (' . $uri['filesource'] . ')'; + } + } + } + + $info['Release Notes'] = $obj->getNotes(); + if ($compat = $obj->getCompatible()) { + if (!isset($compat[0])) { + $compat = array($compat); + } + + $info['Compatible with'] = ''; + foreach ($compat as $package) { + $info['Compatible with'] .= $package['channel'] . '/' . $package['name'] . + "\nVersions >= " . $package['min'] . ', <= ' . $package['max']; + if (isset($package['exclude'])) { + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= $package['channel'] . '/' . + $package['name'] . "\nVersions " . $package['exclude']; + } + } + } + + $usesrole = $obj->getUsesrole(); + if ($usesrole) { + if (!isset($usesrole[0])) { + $usesrole = array($usesrole); + } + + foreach ($usesrole as $roledata) { + if (isset($info['Uses Custom Roles'])) { + $info['Uses Custom Roles'] .= "\n"; + } else { + $info['Uses Custom Roles'] = ''; + } + + if (isset($roledata['package'])) { + $rolepackage = $reg->parsedPackageNameToString($roledata, true); + } else { + $rolepackage = $roledata['uri']; + } + $info['Uses Custom Roles'] .= $roledata['role'] . ' (' . $rolepackage . ')'; + } + } + + $usestask = $obj->getUsestask(); + if ($usestask) { + if (!isset($usestask[0])) { + $usestask = array($usestask); + } + + foreach ($usestask as $taskdata) { + if (isset($info['Uses Custom Tasks'])) { + $info['Uses Custom Tasks'] .= "\n"; + } else { + $info['Uses Custom Tasks'] = ''; + } + + if (isset($taskdata['package'])) { + $taskpackage = $reg->parsedPackageNameToString($taskdata, true); + } else { + $taskpackage = $taskdata['uri']; + } + $info['Uses Custom Tasks'] .= $taskdata['task'] . ' (' . $taskpackage . ')'; + } + } + + $deps = $obj->getDependencies(); + $info['Required Dependencies'] = 'PHP version ' . $deps['required']['php']['min']; + if (isset($deps['required']['php']['max'])) { + $info['Required Dependencies'] .= '-' . $deps['required']['php']['max'] . "\n"; + } else { + $info['Required Dependencies'] .= "\n"; + } + + if (isset($deps['required']['php']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['php']['exclude'])) { + $deps['required']['php']['exclude'] = + implode(', ', $deps['required']['php']['exclude']); + } + $info['Not Compatible with'] .= "PHP versions\n " . + $deps['required']['php']['exclude']; + } + + $info['Required Dependencies'] .= 'PEAR installer version'; + if (isset($deps['required']['pearinstaller']['max'])) { + $info['Required Dependencies'] .= 's ' . + $deps['required']['pearinstaller']['min'] . '-' . + $deps['required']['pearinstaller']['max']; + } else { + $info['Required Dependencies'] .= ' ' . + $deps['required']['pearinstaller']['min'] . ' or newer'; + } + + if (isset($deps['required']['pearinstaller']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['pearinstaller']['exclude'])) { + $deps['required']['pearinstaller']['exclude'] = + implode(', ', $deps['required']['pearinstaller']['exclude']); + } + $info['Not Compatible with'] .= "PEAR installer\n Versions " . + $deps['required']['pearinstaller']['exclude']; + } + + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['required'][$index])) { + if (isset($deps['required'][$index]['name'])) { + $deps['required'][$index] = array($deps['required'][$index]); + } + + foreach ($deps['required'][$index] as $package) { + if (isset($package['conflicts'])) { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Required Dependencies'; + $info[$infoindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['max'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $package['package'] = $package['name']; // for parsedPackageNameToString + if (isset($package['conflicts'])) { + $info['Not Compatible with'] .= '=> except '; + } + $info['Not Compatible with'] .= 'Package ' . + $reg->parsedPackageNameToString($package, true); + $info['Not Compatible with'] .= "\n Versions " . $package['exclude']; + } + } + } + } + + if (isset($deps['required']['os'])) { + if (isset($deps['required']['os']['name'])) { + $dep['required']['os']['name'] = array($dep['required']['os']['name']); + } + + foreach ($dep['required']['os'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "$os[name] Operating System"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "$os[name] Operating System"; + } + } + } + + if (isset($deps['required']['arch'])) { + if (isset($deps['required']['arch']['pattern'])) { + $dep['required']['arch']['pattern'] = array($dep['required']['os']['pattern']); + } + + foreach ($dep['required']['arch'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } + } + } + + if (isset($deps['optional'])) { + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['optional'][$index])) { + if (isset($deps['optional'][$index]['name'])) { + $deps['optional'][$index] = array($deps['optional'][$index]); + } + + foreach ($deps['optional'][$index] as $package) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Optional Dependencies'; + if (!isset($info['Optional Dependencies'])) { + $info['Optional Dependencies'] = ''; + } else { + $info['Optional Dependencies'] .= "\n"; + } + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if ($infoindex == 'Not Compatible with') { + // conflicts is only used to say that all versions conflict + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $info['Not Compatible with'] .= "Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + $info['Dependency Group ' . $group['attribs']['name']] = $group['attribs']['hint']; + $groupindex = $group['attribs']['name'] . ' Contents'; + $info[$groupindex] = ''; + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($group[$index])) { + if (isset($group[$index]['name'])) { + $group[$index] = array($group[$index]); + } + + foreach ($group[$index] as $package) { + if (!empty($info[$groupindex])) { + $info[$groupindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + if (isset($package['uri'])) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + } else { + $info[$groupindex] .= "$type $name"; + } + + $info[$groupindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + continue; + } + + $info[$groupindex] .= "$type $name"; + if (isset($package['max']) && isset($package['min'])) { + $info[$groupindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$groupindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info[$groupindex] .= "Not Compatible with\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + $info[$groupindex] .= " Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + } + + if ($obj->getPackageType() == 'bundle') { + $info['Bundled Packages'] = ''; + foreach ($obj->getBundledPackages() as $package) { + if (!empty($info['Bundled Packages'])) { + $info['Bundled Packages'] .= "\n"; + } + + if (isset($package['uri'])) { + $info['Bundled Packages'] .= '__uri/' . $package['name']; + $info['Bundled Packages'] .= "\n (URI: $package[uri]"; + } else { + $info['Bundled Packages'] .= $package['channel'] . '/' . $package['name']; + } + } + } + + $info['package.xml version'] = '2.0'; + if ($installed) { + if ($obj->getLastModified()) { + $info['Last Modified'] = date('Y-m-d H:i', $obj->getLastModified()); + } + + $v = $obj->getLastInstalledVersion(); + $info['Previous Installed Version'] = $v ? $v : '- None -'; + } + + foreach ($info as $key => $value) { + $data['data'][] = array($key, $value); + } + + $data['raw'] = $obj->getArray(); // no validation needed + $this->ui->outputData($data, 'package-info'); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Registry.xml b/includes/pear/PEAR/Command/Registry.xml new file mode 100644 index 0000000..9f4e214 --- /dev/null +++ b/includes/pear/PEAR/Command/Registry.xml @@ -0,0 +1,58 @@ +<commands version="1.0"> + <list> + <summary>List Installed Packages In The Default Channel</summary> + <function>doList</function> + <shortcut>l</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>list installed packages from this channel</doc> + <arg>CHAN</arg> + </channel> + <allchannels> + <shortopt>a</shortopt> + <doc>list installed packages from all channels</doc> + </allchannels> + <channelinfo> + <shortopt>i</shortopt> + <doc>output fully channel-aware data, even on failure</doc> + </channelinfo> + </options> + <doc><package> +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. +</doc> + </list> + <list-files> + <summary>List Files In Installed Package</summary> + <function>doFileList</function> + <shortcut>fl</shortcut> + <options /> + <doc><package> +List the files in an installed package. +</doc> + </list-files> + <shell-test> + <summary>Shell Script Test</summary> + <function>doShellTest</function> + <shortcut>st</shortcut> + <options /> + <doc><package> [[relation] version] +Tests if a package is installed in the system. Will exit(1) if it is not. + <relation> The version comparison operator. One of: + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + <version> The version to compare with +</doc> + </shell-test> + <info> + <summary>Display information about a package</summary> + <function>doInfo</function> + <shortcut>in</shortcut> + <options /> + <doc><package> +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package.</doc> + </info> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Remote.php b/includes/pear/PEAR/Command/Remote.php new file mode 100644 index 0000000..52d0aee --- /dev/null +++ b/includes/pear/PEAR/Command/Remote.php @@ -0,0 +1,810 @@ +<?php +/** + * PEAR_Command_Remote (remote-info, list-upgrades, remote-list, search, list-all, download, + * clear-cache commands) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; +require_once 'PEAR/REST.php'; + +/** + * PEAR commands for remote server querying + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Remote extends PEAR_Command_Common +{ + var $commands = array( + 'remote-info' => array( + 'summary' => 'Information About Remote Packages', + 'function' => 'doRemoteInfo', + 'shortcut' => 'ri', + 'options' => array(), + 'doc' => '<package> +Get details on a package from the server.', + ), + 'list-upgrades' => array( + 'summary' => 'List Available Upgrades', + 'function' => 'doListUpgrades', + 'shortcut' => 'lu', + 'options' => array( + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter.' + ), + 'remote-list' => array( + 'summary' => 'List Remote Packages', + 'function' => 'doRemoteList', + 'shortcut' => 'rl', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ) + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'search' => array( + 'summary' => 'Search remote package database', + 'function' => 'doSearch', + 'shortcut' => 'sp', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'search packages from all known channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description', + ), + 'list-all' => array( + 'summary' => 'List All Packages', + 'function' => 'doListAll', + 'shortcut' => 'la', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'download' => array( + 'summary' => 'Download Package', + 'function' => 'doDownload', + 'shortcut' => 'd', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'download an uncompressed (.tar) file', + ), + ), + 'doc' => '<package>... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.', + ), + 'clear-cache' => array( + 'summary' => 'Clear Web Services Cache', + 'function' => 'doClearCache', + 'shortcut' => 'cc', + 'options' => array(), + 'doc' => ' +Clear the REST cache. See also the cache_ttl configuration +parameter. +', + ), + ); + + /** + * PEAR_Command_Remote constructor. + * + * @access public + */ + function PEAR_Command_Remote(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _checkChannelForStatus($channel, $chan) + { + if (PEAR::isError($chan)) { + $this->raiseError($chan); + } + if (!is_a($chan, 'PEAR_ChannelFile')) { + return $this->raiseError('Internal corruption error: invalid channel "' . + $channel . '"'); + } + $rest = new PEAR_REST($this->config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $mirror = $this->config->get('preferred_mirror', null, + $channel); + $a = $rest->downloadHttp('http://' . $channel . + '/channel.xml', $chan->lastModified()); + PEAR::staticPopErrorHandling(); + if (!PEAR::isError($a) && $a) { + $this->ui->outputData('WARNING: channel "' . $channel . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel . + '" to update'); + } + } + + function doRemoteInfo($command, $options, $params) + { + if (sizeof($params) != 1) { + return $this->raiseError("$command expects one param: the remote package name"); + } + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $package = $params[0]; + $parsed = $reg->parsePackageName($package, $channel); + if (PEAR::isError($parsed)) { + return $this->raiseError('Invalid package name "' . $package . '"'); + } + + $channel = $parsed['channel']; + $this->config->set('default_channel', $channel); + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) { + $rest = &$this->config->getREST('1.0', array()); + $info = $rest->packageInfo($base, $parsed['package'], $channel); + } + + if (!isset($info)) { + return $this->raiseError('No supported protocol was found'); + } + + if (PEAR::isError($info)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($info); + } + + if (!isset($info['name'])) { + return $this->raiseError('No remote package "' . $package . '" was found'); + } + + $installed = $reg->packageInfo($info['name'], null, $channel); + $info['installed'] = $installed['version'] ? $installed['version'] : '- no -'; + if (is_array($info['installed'])) { + $info['installed'] = $info['installed']['release']; + } + + $this->ui->outputData($info, $command); + $this->config->set('default_channel', $savechannel); + + return true; + } + + function doRemoteList($command, $options, $params) + { + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $list_options = false; + if ($this->config->get('preferred_state') == 'stable') { + $list_options = true; + } + + $available = array(); + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror')) + ) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($available); + } + + $i = $j = 0; + $data = array( + 'caption' => 'Channel ' . $channel . ' Available packages:', + 'border' => true, + 'headline' => array('Package', 'Version'), + 'channel' => $channel + ); + + if (count($available) == 0) { + $data = '(no packages available yet)'; + } else { + foreach ($available as $name => $info) { + $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-'; + $data['data'][] = array($name, $version); + } + } + $this->ui->outputData($data, $command); + $this->config->set('default_channel', $savechannel); + return true; + } + + function doListAll($command, $options, $params) + { + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError("Channel \"$channel\" does not exist"); + } + + $this->config->set('default_channel', $channel); + } + + $list_options = false; + if ($this->config->get('preferred_state') == 'stable') { + $list_options = true; + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")'); + } + + $data = array( + 'caption' => 'All packages [Channel ' . $channel . ']:', + 'border' => true, + 'headline' => array('Package', 'Latest', 'Local'), + 'channel' => $channel, + ); + + if (isset($options['channelinfo'])) { + // add full channelinfo + $data['caption'] = 'Channel ' . $channel . ' All packages:'; + $data['headline'] = array('Channel', 'Package', 'Latest', 'Local', + 'Description', 'Dependencies'); + } + $local_pkgs = $reg->listPackages($channel); + + foreach ($available as $name => $info) { + $installed = $reg->packageInfo($name, null, $channel); + if (is_array($installed['version'])) { + $installed['version'] = $installed['version']['release']; + } + $desc = $info['summary']; + if (isset($params[$name])) { + $desc .= "\n\n".$info['description']; + } + + if (isset($options['mode'])) { + if ($options['mode'] == 'installed' && !isset($installed['version'])) { + continue; + } + if ($options['mode'] == 'notinstalled' && isset($installed['version'])) { + continue; + } + if ($options['mode'] == 'upgrades' + && (!isset($installed['version']) || version_compare($installed['version'], + $info['stable'], '>='))) { + continue; + } + } + $pos = array_search(strtolower($name), $local_pkgs); + if ($pos !== false) { + unset($local_pkgs[$pos]); + } + + if (isset($info['stable']) && !$info['stable']) { + $info['stable'] = null; + } + + if (isset($options['channelinfo'])) { + // add full channelinfo + if ($info['stable'] === $info['unstable']) { + $state = $info['state']; + } else { + $state = 'stable'; + } + $latest = $info['stable'].' ('.$state.')'; + $local = ''; + if (isset($installed['version'])) { + $inst_state = $reg->packageInfo($name, 'release_state', $channel); + $local = $installed['version'].' ('.$inst_state.')'; + } + + $packageinfo = array( + $channel, + $name, + $latest, + $local, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, + ); + } else { + $packageinfo = array( + $reg->channelAlias($channel) . '/' . $name, + isset($info['stable']) ? $info['stable'] : null, + isset($installed['version']) ? $installed['version'] : null, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, + ); + } + $data['data'][$info['category']][] = $packageinfo; + } + + if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) { + $this->config->set('default_channel', $savechannel); + $this->ui->outputData($data, $command); + return true; + } + + foreach ($local_pkgs as $name) { + $info = &$reg->getPackage($name, $channel); + $data['data']['Local'][] = array( + $reg->channelAlias($channel) . '/' . $info->getPackage(), + '', + $info->getVersion(), + $info->getSummary(), + $info->getDeps() + ); + } + + $this->config->set('default_channel', $savechannel); + $this->ui->outputData($data, $command); + return true; + } + + function doSearch($command, $options, $params) + { + if ((!isset($params[0]) || empty($params[0])) + && (!isset($params[1]) || empty($params[1]))) + { + return $this->raiseError('no valid search string supplied'); + } + + $channelinfo = isset($options['channelinfo']); + $reg = &$this->config->getRegistry(); + if (isset($options['allchannels'])) { + // search all channels + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + $options['channel'] = $channel->getName(); + $ret = $this->doSearch($command, $options, $params); + if (PEAR::isError($ret)) { + $errors[] = $ret; + } + } + } + + PEAR::staticPopErrorHandling(); + if (count($errors) !== 0) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + $savechannel = $channel = $this->config->get('default_channel'); + $package = strtolower($params[0]); + $summary = isset($params[1]) ? $params[1] : false; + if (isset($options['channel'])) { + $reg = &$this->config->getRegistry(); + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($available); + } + + if (!$available && !$channelinfo) { + // clean exit when not found, no error ! + $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.'; + $this->ui->outputData($data); + $this->config->set('default_channel', $channel); + return true; + } + + if ($channelinfo) { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } else { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } + + if (!$available && $channelinfo) { + unset($data['headline']); + $data['data'] = 'No packages found that match pattern "' . $package . '".'; + $available = array(); + } + + foreach ($available as $name => $info) { + $installed = $reg->packageInfo($name, null, $channel); + $desc = $info['summary']; + if (isset($params[$name])) + $desc .= "\n\n".$info['description']; + + if (!isset($info['stable']) || !$info['stable']) { + $version_remote = 'none'; + } else { + if ($info['unstable']) { + $version_remote = $info['unstable']; + } else { + $version_remote = $info['stable']; + } + $version_remote .= ' ('.$info['state'].')'; + } + $version = is_array($installed['version']) ? $installed['version']['release'] : + $installed['version']; + if ($channelinfo) { + $packageinfo = array( + $channel, + $name, + $version_remote, + $version, + $desc, + ); + } else { + $packageinfo = array( + $name, + $version_remote, + $version, + $desc, + ); + } + $data['data'][$info['category']][] = $packageinfo; + } + + $this->ui->outputData($data, $command); + $this->config->set('default_channel', $channel); + return true; + } + + function &getDownloader($options) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($this->ui, $options, $this->config); + return $a; + } + + function doDownload($command, $options, $params) + { + // make certain that dependencies are ignored + $options['downloadonly'] = 1; + + // eliminate error messages for preferred_state-related errors + /* TODO: Should be an option, but until now download does respect + prefered state */ + /* $options['ignorepreferred_state'] = 1; */ + // eliminate error messages for preferred_state-related errors + + $downloader = &$this->getDownloader($options); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $e = $downloader->setDownloadDir(getcwd()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + return $this->raiseError('Current directory is not writeable, cannot download'); + } + + $errors = array(); + $downloaded = array(); + $err = $downloader->download($params); + if (PEAR::isError($err)) { + return $err; + } + + $errors = $downloader->getErrorMsgs(); + if (count($errors)) { + foreach ($errors as $error) { + if ($error !== null) { + $this->ui->outputData($error); + } + } + + return $this->raiseError("$command failed"); + } + + $downloaded = $downloader->getDownloadedPackages(); + foreach ($downloaded as $pkg) { + $this->ui->outputData("File $pkg[file] downloaded", $command); + } + + return true; + } + + function downloadCallback($msg, $params = null) + { + if ($msg == 'done') { + $this->bytes_downloaded = $params; + } + } + + function doListUpgrades($command, $options, $params) + { + require_once 'PEAR/Common.php'; + if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) { + return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"'); + } + + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + foreach ($reg->listChannels() as $channel) { + $inst = array_flip($reg->listPackages($channel)); + if (!count($inst)) { + continue; + } + + if ($channel == '__uri') { + continue; + } + + $this->config->set('default_channel', $channel); + $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0]; + + $caption = $channel . ' Available Upgrades'; + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $latest = array(); + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + + ) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + if (empty($state) || $state == 'any') { + $state = false; + } else { + $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')'; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg); + PEAR::staticPopErrorHandling(); + } + + if (PEAR::isError($latest)) { + $this->ui->outputData($latest->getMessage()); + continue; + } + + $caption .= ':'; + if (PEAR::isError($latest)) { + $this->config->set('default_channel', $savechannel); + return $latest; + } + + $data = array( + 'caption' => $caption, + 'border' => 1, + 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'), + 'channel' => $channel + ); + + foreach ((array)$latest as $pkg => $info) { + $package = strtolower($pkg); + if (!isset($inst[$package])) { + // skip packages we don't have installed + continue; + } + + extract($info); + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + if (version_compare("$version", "$inst_version", "le")) { + // installed version is up-to-date + continue; + } + + if ($filesize >= 20480) { + $filesize += 1024 - ($filesize % 1024); + $fs = sprintf("%dkB", $filesize / 1024); + } elseif ($filesize > 0) { + $filesize += 103 - ($filesize % 103); + $fs = sprintf("%.1fkB", $filesize / 1024.0); + } else { + $fs = " -"; // XXX center instead + } + + $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs); + } + + if (isset($options['channelinfo'])) { + if (empty($data['data'])) { + unset($data['headline']); + if (count($inst) == 0) { + $data['data'] = '(no packages installed)'; + } else { + $data['data'] = '(no upgrades available)'; + } + } + $this->ui->outputData($data, $command); + } else { + if (empty($data['data'])) { + $this->ui->outputData('Channel ' . $channel . ': No upgrades available'); + } else { + $this->ui->outputData($data, $command); + } + } + } + + $this->config->set('default_channel', $savechannel); + return true; + } + + function doClearCache($command, $options, $params) + { + $cache_dir = $this->config->get('cache_dir'); + $verbose = $this->config->get('verbose'); + $output = ''; + if (!file_exists($cache_dir) || !is_dir($cache_dir)) { + return $this->raiseError("$cache_dir does not exist or is not a directory"); + } + + if (!($dp = @opendir($cache_dir))) { + return $this->raiseError("opendir($cache_dir) failed: $php_errormsg"); + } + + if ($verbose >= 1) { + $output .= "reading directory $cache_dir\n"; + } + + $num = 0; + while ($ent = readdir($dp)) { + if (preg_match('/rest.cache(file|id)\\z/', $ent)) { + $path = $cache_dir . DIRECTORY_SEPARATOR . $ent; + if (file_exists($path)) { + $ok = @unlink($path); + } else { + $ok = false; + $php_errormsg = ''; + } + + if ($ok) { + if ($verbose >= 2) { + $output .= "deleted $path\n"; + } + $num++; + } elseif ($verbose >= 1) { + $output .= "failed to delete $path $php_errormsg\n"; + } + } + } + + closedir($dp); + if ($verbose >= 1) { + $output .= "$num cache entries cleared\n"; + } + + $this->ui->outputData(rtrim($output), $command); + return $num; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Remote.xml b/includes/pear/PEAR/Command/Remote.xml new file mode 100644 index 0000000..b4f6100 --- /dev/null +++ b/includes/pear/PEAR/Command/Remote.xml @@ -0,0 +1,109 @@ +<commands version="1.0"> + <remote-info> + <summary>Information About Remote Packages</summary> + <function>doRemoteInfo</function> + <shortcut>ri</shortcut> + <options /> + <doc><package> +Get details on a package from the server.</doc> + </remote-info> + <list-upgrades> + <summary>List Available Upgrades</summary> + <function>doListUpgrades</function> + <shortcut>lu</shortcut> + <options> + <channelinfo> + <shortopt>i</shortopt> + <doc>output fully channel-aware data, even on failure</doc> + </channelinfo> + </options> + <doc>[preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter.</doc> + </list-upgrades> + <remote-list> + <summary>List Remote Packages</summary> + <function>doRemoteList</function> + <shortcut>rl</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>specify a channel other than the default channel</doc> + <arg>CHAN</arg> + </channel> + </options> + <doc> +Lists the packages available on the configured server along with the +latest stable release of each package.</doc> + </remote-list> + <search> + <summary>Search remote package database</summary> + <function>doSearch</function> + <shortcut>sp</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>specify a channel other than the default channel</doc> + <arg>CHAN</arg> + </channel> + <allchannels> + <shortopt>a</shortopt> + <doc>search packages from all known channels</doc> + </allchannels> + <channelinfo> + <shortopt>i</shortopt> + <doc>output fully channel-aware data, even on failure</doc> + </channelinfo> + </options> + <doc>[packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description</doc> + </search> + <list-all> + <summary>List All Packages</summary> + <function>doListAll</function> + <shortcut>la</shortcut> + <options> + <channel> + <shortopt>c</shortopt> + <doc>specify a channel other than the default channel</doc> + <arg>CHAN</arg> + </channel> + <channelinfo> + <shortopt>i</shortopt> + <doc>output fully channel-aware data, even on failure</doc> + </channelinfo> + </options> + <doc> +Lists the packages available on the configured server along with the +latest stable release of each package.</doc> + </list-all> + <download> + <summary>Download Package</summary> + <function>doDownload</function> + <shortcut>d</shortcut> + <options> + <nocompress> + <shortopt>Z</shortopt> + <doc>download an uncompressed (.tar) file</doc> + </nocompress> + </options> + <doc><package>... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.</doc> + </download> + <clear-cache> + <summary>Clear Web Services Cache</summary> + <function>doClearCache</function> + <shortcut>cc</shortcut> + <options /> + <doc> +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. +</doc> + </clear-cache> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Test.php b/includes/pear/PEAR/Command/Test.php new file mode 100644 index 0000000..fcfa6f6 --- /dev/null +++ b/includes/pear/PEAR/Command/Test.php @@ -0,0 +1,337 @@ +<?php +/** + * PEAR_Command_Test (run-tests) + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Martin Jansen <mj@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Martin Jansen <mj@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + +class PEAR_Command_Test extends PEAR_Command_Common +{ + var $commands = array( + 'run-tests' => array( + 'summary' => 'Run Regression Tests', + 'function' => 'doRunTests', + 'shortcut' => 'rt', + 'options' => array( + 'recur' => array( + 'shortopt' => 'r', + 'doc' => 'Run tests in child directories, recursively. 4 dirs deep maximum', + ), + 'ini' => array( + 'shortopt' => 'i', + 'doc' => 'actual string of settings to pass to php in format " -d setting=blah"', + 'arg' => 'SETTINGS' + ), + 'realtimelog' => array( + 'shortopt' => 'l', + 'doc' => 'Log test runs/results as they are run', + ), + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Only display detail for failed tests', + ), + 'simple' => array( + 'shortopt' => 's', + 'doc' => 'Display simple output for all tests', + ), + 'package' => array( + 'shortopt' => 'p', + 'doc' => 'Treat parameters as installed packages from which to run tests', + ), + 'phpunit' => array( + 'shortopt' => 'u', + 'doc' => 'Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead.', + ), + 'tapoutput' => array( + 'shortopt' => 't', + 'doc' => 'Output run-tests.log in TAP-compliant format', + ), + 'cgi' => array( + 'shortopt' => 'c', + 'doc' => 'CGI php executable (needed for tests with POST/GET section)', + 'arg' => 'PHPCGI', + ), + 'coverage' => array( + 'shortopt' => 'x', + 'doc' => 'Generate a code coverage report (requires Xdebug 2.0.0+)', + ), + ), + 'doc' => '[testfile|dir ...] +Run regression tests with PHP\'s regression testing script (run-tests.php).', + ), + ); + + var $output; + + /** + * PEAR_Command_Test constructor. + * + * @access public + */ + function PEAR_Command_Test(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doRunTests($command, $options, $params) + { + if (isset($options['phpunit']) && isset($options['tapoutput'])) { + return $this->raiseError('ERROR: cannot use both --phpunit and --tapoutput at the same time'); + } + + require_once 'PEAR/Common.php'; + require_once 'System.php'; + $log = new PEAR_Common; + $log->ui = &$this->ui; // slightly hacky, but it will work + $tests = array(); + $depth = isset($options['recur']) ? 14 : 1; + + if (!count($params)) { + $params[] = '.'; + } + + if (isset($options['package'])) { + $oldparams = $params; + $params = array(); + $reg = &$this->config->getRegistry(); + foreach ($oldparams as $param) { + $pname = $reg->parsePackageName($param, $this->config->get('default_channel')); + if (PEAR::isError($pname)) { + return $this->raiseError($pname); + } + + $package = &$reg->getPackage($pname['package'], $pname['channel']); + if (!$package) { + return PEAR::raiseError('Unknown package "' . + $reg->parsedPackageNameToString($pname) . '"'); + } + + $filelist = $package->getFilelist(); + foreach ($filelist as $name => $atts) { + if (isset($atts['role']) && $atts['role'] != 'test') { + continue; + } + + if (isset($options['phpunit']) && preg_match('/AllTests\.php\\z/i', $name)) { + $params[] = $atts['installed_as']; + continue; + } elseif (!preg_match('/\.phpt\\z/', $name)) { + continue; + } + $params[] = $atts['installed_as']; + } + } + } + + foreach ($params as $p) { + if (is_dir($p)) { + if (isset($options['phpunit'])) { + $dir = System::find(array($p, '-type', 'f', + '-maxdepth', $depth, + '-name', 'AllTests.php')); + if (count($dir)) { + foreach ($dir as $p) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + } + continue; + } + + $args = array($p, '-type', 'f', '-name', '*.phpt'); + } else { + if (isset($options['phpunit'])) { + if (preg_match('/AllTests\.php\\z/i', $p)) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + continue; + } + + if (file_exists($p) && preg_match('/\.phpt$/', $p)) { + $tests[] = $p; + continue; + } + + if (!preg_match('/\.phpt\\z/', $p)) { + $p .= '.phpt'; + } + + $args = array(dirname($p), '-type', 'f', '-name', $p); + } + + if (!isset($options['recur'])) { + $args[] = '-maxdepth'; + $args[] = 1; + } + + $dir = System::find($args); + $tests = array_merge($tests, $dir); + } + + $ini_settings = ''; + if (isset($options['ini'])) { + $ini_settings .= $options['ini']; + } + + if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) { + $ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}"; + } + + if ($ini_settings) { + $this->ui->outputData('Using INI settings: "' . $ini_settings . '"'); + } + + $skipped = $passed = $failed = array(); + $tests_count = count($tests); + $this->ui->outputData('Running ' . $tests_count . ' tests', $command); + $start = time(); + if (isset($options['realtimelog']) && file_exists('run-tests.log')) { + unlink('run-tests.log'); + } + + if (isset($options['tapoutput'])) { + $tap = '1..' . $tests_count . "\n"; + } + + require_once 'PEAR/RunTest.php'; + $run = new PEAR_RunTest($log, $options); + $run->tests_count = $tests_count; + + if (isset($options['coverage']) && extension_loaded('xdebug')){ + $run->xdebug_loaded = true; + } else { + $run->xdebug_loaded = false; + } + + $j = $i = 1; + foreach ($tests as $t) { + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "Running test [$i / $tests_count] $t..."); + fclose($fp); + } + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (isset($options['phpunit'])) { + $result = $run->runPHPUnit($t, $ini_settings); + } else { + $result = $run->run($t, $ini_settings, $j); + } + PEAR::staticPopErrorHandling(); + if (PEAR::isError($result)) { + $this->ui->log($result->getMessage()); + continue; + } + + if (isset($options['tapoutput'])) { + $tap .= $result[0] . ' ' . $i . $result[1] . "\n"; + continue; + } + + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "$result\n"); + fclose($fp); + } + } + + if ($result == 'FAILED') { + $failed[] = $t; + } + if ($result == 'PASSED') { + $passed[] = $t; + } + if ($result == 'SKIPPED') { + $skipped[] = $t; + } + + $j++; + } + + $total = date('i:s', time() - $start); + if (isset($options['tapoutput'])) { + $fp = @fopen('run-tests.log', 'w'); + if ($fp) { + fwrite($fp, $tap, strlen($tap)); + fclose($fp); + $this->ui->outputData('wrote TAP-format log to "' .realpath('run-tests.log') . + '"', $command); + } + } else { + if (count($failed)) { + $output = "TOTAL TIME: $total\n"; + $output .= count($passed) . " PASSED TESTS\n"; + $output .= count($skipped) . " SKIPPED TESTS\n"; + $output .= count($failed) . " FAILED TESTS:\n"; + foreach ($failed as $failure) { + $output .= $failure . "\n"; + } + + $mode = isset($options['realtimelog']) ? 'a' : 'w'; + $fp = @fopen('run-tests.log', $mode); + + if ($fp) { + fwrite($fp, $output, strlen($output)); + fclose($fp); + $this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command); + } + } elseif (file_exists('run-tests.log') && !is_dir('run-tests.log')) { + @unlink('run-tests.log'); + } + } + $this->ui->outputData('TOTAL TIME: ' . $total); + $this->ui->outputData(count($passed) . ' PASSED TESTS', $command); + $this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command); + if (count($failed)) { + $this->ui->outputData(count($failed) . ' FAILED TESTS:', $command); + foreach ($failed as $failure) { + $this->ui->outputData($failure, $command); + } + } + + return true; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Command/Test.xml b/includes/pear/PEAR/Command/Test.xml new file mode 100644 index 0000000..bbe9fcc --- /dev/null +++ b/includes/pear/PEAR/Command/Test.xml @@ -0,0 +1,54 @@ +<commands version="1.0"> + <run-tests> + <summary>Run Regression Tests</summary> + <function>doRunTests</function> + <shortcut>rt</shortcut> + <options> + <recur> + <shortopt>r</shortopt> + <doc>Run tests in child directories, recursively. 4 dirs deep maximum</doc> + </recur> + <ini> + <shortopt>i</shortopt> + <doc>actual string of settings to pass to php in format " -d setting=blah"</doc> + <arg>SETTINGS</arg> + </ini> + <realtimelog> + <shortopt>l</shortopt> + <doc>Log test runs/results as they are run</doc> + </realtimelog> + <quiet> + <shortopt>q</shortopt> + <doc>Only display detail for failed tests</doc> + </quiet> + <simple> + <shortopt>s</shortopt> + <doc>Display simple output for all tests</doc> + </simple> + <package> + <shortopt>p</shortopt> + <doc>Treat parameters as installed packages from which to run tests</doc> + </package> + <phpunit> + <shortopt>u</shortopt> + <doc>Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead.</doc> + </phpunit> + <tapoutput> + <shortopt>t</shortopt> + <doc>Output run-tests.log in TAP-compliant format</doc> + </tapoutput> + <cgi> + <shortopt>c</shortopt> + <doc>CGI php executable (needed for tests with POST/GET section)</doc> + <arg>PHPCGI</arg> + </cgi> + <coverage> + <shortopt>x</shortopt> + <doc>Generate a code coverage report (requires Xdebug 2.0.0+)</doc> + </coverage> + </options> + <doc>[testfile|dir ...] +Run regression tests with PHP's regression testing script (run-tests.php).</doc> + </run-tests> +</commands>
\ No newline at end of file diff --git a/includes/pear/PEAR/Common.php b/includes/pear/PEAR/Common.php new file mode 100644 index 0000000..f98bf2c --- /dev/null +++ b/includes/pear/PEAR/Common.php @@ -0,0 +1,837 @@ +<?php +/** + * PEAR_Common, the base class for the PEAR Installer + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1.0 + * @deprecated File deprecated since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +require_once 'PEAR.php'; + +/** + * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode() + */ +define('PEAR_COMMON_ERROR_INVALIDPHP', 1); +define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+'); +define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/'); + +// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1 +define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?'); +define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '\\z/i'); + +// XXX far from perfect :-) +define('_PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '(' . _PEAR_COMMON_PACKAGE_NAME_PREG . + ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_PACKAGE_DOWNLOAD_PREG . + '\\z/'); + +define('_PEAR_CHANNELS_NAME_PREG', '[A-Za-z][a-zA-Z0-9\.]+'); +define('PEAR_CHANNELS_NAME_PREG', '/^' . _PEAR_CHANNELS_NAME_PREG . '\\z/'); + +// this should allow any dns or IP address, plus a path - NO UNDERSCORES ALLOWED +define('_PEAR_CHANNELS_SERVER_PREG', '[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(\/[a-zA-Z0-9\-]+)*'); +define('PEAR_CHANNELS_SERVER_PREG', '/^' . _PEAR_CHANNELS_SERVER_PREG . '\\z/i'); + +define('_PEAR_CHANNELS_PACKAGE_PREG', '(' ._PEAR_CHANNELS_SERVER_PREG . ')\/(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')'); +define('PEAR_CHANNELS_PACKAGE_PREG', '/^' . _PEAR_CHANNELS_PACKAGE_PREG . '\\z/i'); + +define('_PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '(' . _PEAR_CHANNELS_NAME_PREG . ')::(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_CHANNEL_DOWNLOAD_PREG . '\\z/'); + +/** + * List of temporary files and directories registered by + * PEAR_Common::addTempFile(). + * @var array + */ +$GLOBALS['_PEAR_Common_tempfiles'] = array(); + +/** + * Valid maintainer roles + * @var array + */ +$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper'); + +/** + * Valid release states + * @var array + */ +$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel'); + +/** + * Valid dependency types + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi'); + +/** + * Valid dependency relations + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne'); + +/** + * Valid file roles + * @var array + */ +$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script'); + +/** + * Valid replacement types + * @var array + */ +$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup'); + +/** + * Class providing common functionality for PEAR administration classes. + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @deprecated This class will disappear, and its components will be spread + * into smaller classes, like the AT&T breakup, as of Release 1.4.0a1 + */ +class PEAR_Common extends PEAR +{ + /** + * User Interface object (PEAR_Frontend_* class). If null, + * the log() method uses print. + * @var object + */ + var $ui = null; + + /** + * Configuration object (PEAR_Config). + * @var PEAR_Config + */ + var $config = null; + + /** stack of elements, gives some sort of XML context */ + var $element_stack = array(); + + /** name of currently parsed XML element */ + var $current_element; + + /** array of attributes of the currently parsed XML element */ + var $current_attributes = array(); + + /** assoc with information about a package */ + var $pkginfo = array(); + + var $current_path = null; + + /** + * Flag variable used to mark a valid package file + * @var boolean + * @access private + */ + var $_validPackageFile; + + /** + * PEAR_Common constructor + * + * @access public + */ + function PEAR_Common() + { + parent::PEAR(); + $this->config = &PEAR_Config::singleton(); + $this->debug = $this->config->get('verbose'); + } + + /** + * PEAR_Common destructor + * + * @access private + */ + function _PEAR_Common() + { + // doesn't work due to bug #14744 + //$tempfiles = $this->_tempfiles; + $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles']; + while ($file = array_shift($tempfiles)) { + if (@is_dir($file)) { + if (!class_exists('System')) { + require_once 'System.php'; + } + + System::rm(array('-rf', $file)); + } elseif (file_exists($file)) { + unlink($file); + } + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * + * @return void + * + * @access public + */ + function addTempFile($file) + { + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + PEAR_Frontend::addTempFile($file); + } + + /** + * Wrapper to System::mkDir(), creates a directory as well as + * any necessary parent directories. + * + * @param string $dir directory name + * + * @return bool TRUE on success, or a PEAR error + * + * @access public + */ + function mkDirHier($dir) + { + // Only used in Installer - move it there ? + $this->log(2, "+ create dir $dir"); + if (!class_exists('System')) { + require_once 'System.php'; + } + return System::mkDir(array('-p', $dir)); + } + + /** + * Logging method. + * + * @param int $level log level (0 is quiet, higher is noisier) + * @param string $msg message to write to the log + * + * @return void + * + * @access public + * @static + */ + function log($level, $msg, $append_crlf = true) + { + if ($this->debug >= $level) { + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + + $ui = &PEAR_Frontend::singleton(); + if (is_a($ui, 'PEAR_Frontend')) { + $ui->log($msg, $append_crlf); + } else { + print "$msg\n"; + } + } + } + + /** + * Create and register a temporary directory. + * + * @param string $tmpdir (optional) Directory to use as tmpdir. + * Will use system defaults (for example + * /tmp or c:\windows\temp) if not specified + * + * @return string name of created directory + * + * @access public + */ + function mkTempDir($tmpdir = '') + { + $topt = $tmpdir ? array('-t', $tmpdir) : array(); + $topt = array_merge($topt, array('-d', 'pear')); + if (!class_exists('System')) { + require_once 'System.php'; + } + + if (!$tmpdir = System::mktemp($topt)) { + return false; + } + + $this->addTempFile($tmpdir); + return $tmpdir; + } + + /** + * Set object that represents the frontend to be used. + * + * @param object Reference of the frontend object + * @return void + * @access public + */ + function setFrontendObject(&$ui) + { + $this->ui = &$ui; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } + + /** + * Get the valid roles for a PEAR package maintainer + * + * @return array + * @static + */ + function getUserRoles() + { + return $GLOBALS['_PEAR_Common_maintainer_roles']; + } + + /** + * Get the valid package release states of packages + * + * @return array + * @static + */ + function getReleaseStates() + { + return $GLOBALS['_PEAR_Common_release_states']; + } + + /** + * Get the implemented dependency types (php, ext, pkg etc.) + * + * @return array + * @static + */ + function getDependencyTypes() + { + return $GLOBALS['_PEAR_Common_dependency_types']; + } + + /** + * Get the implemented dependency relations (has, lt, ge etc.) + * + * @return array + * @static + */ + function getDependencyRelations() + { + return $GLOBALS['_PEAR_Common_dependency_relations']; + } + + /** + * Get the implemented file roles + * + * @return array + * @static + */ + function getFileRoles() + { + return $GLOBALS['_PEAR_Common_file_roles']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getReplacementTypes() + { + return $GLOBALS['_PEAR_Common_replacement_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getProvideTypes() + { + return $GLOBALS['_PEAR_Common_provide_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getScriptPhases() + { + return $GLOBALS['_PEAR_Common_script_phases']; + } + + /** + * Test whether a string contains a valid package name. + * + * @param string $name the package name to test + * + * @return bool + * + * @access public + */ + function validPackageName($name) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name); + } + + /** + * Test whether a string contains a valid package version. + * + * @param string $ver the package version to test + * + * @return bool + * + * @access public + */ + function validPackageVersion($ver) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $ipath = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($ipath as $include) { + $test = realpath($include . DIRECTORY_SEPARATOR . $path); + if (file_exists($test) && is_readable($test)) { + return true; + } + } + + return false; + } + + function _postProcessChecks($pf) + { + if (!PEAR::isError($pf)) { + return $this->_postProcessValidPackagexml($pf); + } + + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + /** + * Returns information about a package file. Expects the name of + * a gzipped tar file as input. + * + * @param string $file name of .tgz file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromTgzFile() instead + * + */ + function infoFromTgzFile($file) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the name of + * a package xml file as input. + * + * @param string $descfile name of package xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromPackageFile() instead + * + */ + function infoFromDescriptionFile($descfile) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the contents + * of a package xml file as input. + * + * @param string $data contents of package.xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromXmlstring() instead + * + */ + function infoFromString($data) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false); + return $this->_postProcessChecks($pf); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return array + */ + function _postProcessValidPackagexml(&$pf) + { + if (!is_a($pf, 'PEAR_PackageFile_v2')) { + $this->pkginfo = $pf->toArray(); + return $this->pkginfo; + } + + // sort of make this into a package.xml 1.0-style array + // changelog is not converted to old format. + $arr = $pf->toArray(true); + $arr = array_merge($arr, $arr['old']); + unset($arr['old'], $arr['xsdversion'], $arr['contents'], $arr['compatible'], + $arr['channel'], $arr['uri'], $arr['dependencies'], $arr['phprelease'], + $arr['extsrcrelease'], $arr['zendextsrcrelease'], $arr['extbinrelease'], + $arr['zendextbinrelease'], $arr['bundle'], $arr['lead'], $arr['developer'], + $arr['helper'], $arr['contributor']); + $arr['filelist'] = $pf->getFilelist(); + $this->pkginfo = $arr; + return $arr; + } + + /** + * Returns package information from different sources + * + * This method is able to extract information about a package + * from a .tgz archive or from a XML package definition file. + * + * @access public + * @param string Filename of the source ('package.xml', '<package>.tgz') + * @return string + * @deprecated use PEAR_PackageFile->fromAnyFile() instead + */ + function infoFromAny($info) + { + if (is_string($info) && file_exists($info)) { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + return $this->_postProcessValidPackagexml($pf); + } + + return $info; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @param array $pkginfo package info + * + * @return string XML data + * + * @access public + * @deprecated use a PEAR_PackageFile_v* object's generator instead + */ + function xmlFromInfo($pkginfo) + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + $pf = &$packagefile->fromArray($pkginfo); + $gen = &$pf->getDefaultGenerator(); + return $gen->toXml(PEAR_VALIDATE_PACKAGING); + } + + /** + * Validate XML package definition file. + * + * @param string $info Filename of the package archive or of the + * package definition file + * @param array $errors Array that will contain the errors + * @param array $warnings Array that will contain the warnings + * @param string $dir_prefix (optional) directory where source files + * may be found, or empty if they are not available + * @access public + * @return boolean + * @deprecated use the validation of PEAR_PackageFile objects + */ + function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '') + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (strpos($info, '<?xml') !== false) { + $pf = &$packagefile->fromXmlString($info, PEAR_VALIDATE_NORMAL, ''); + } else { + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + if ($error['level'] == 'error') { + $errors[] = $error['message']; + } else { + $warnings[] = $error['message']; + } + } + } + + return false; + } + + return true; + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access public + * + */ + function buildProvidesArray($srcinfo) + { + $file = basename($srcinfo['source_file']); + $pn = ''; + if (isset($this->_packageName)) { + $pn = $this->_packageName; + } + + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->pkginfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access public + */ + function analyzeSourceCode($file) + { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + + $a = new PEAR_PackageFile_v2_Validator; + return $a->analyzeSourceCode($file); + } + + function detectDependencies($any, $status_callback = null) + { + if (!function_exists("token_get_all")) { + return false; + } + + if (PEAR::isError($info = $this->infoFromAny($any))) { + return $this->raiseError($info); + } + + if (!is_array($info)) { + return false; + } + + $deps = array(); + $used_c = $decl_c = $decl_f = $decl_m = array(); + foreach ($info['filelist'] as $file => $fa) { + $tmp = $this->analyzeSourceCode($file); + $used_c = @array_merge($used_c, $tmp['used_classes']); + $decl_c = @array_merge($decl_c, $tmp['declared_classes']); + $decl_f = @array_merge($decl_f, $tmp['declared_functions']); + $decl_m = @array_merge($decl_m, $tmp['declared_methods']); + $inheri = @array_merge($inheri, $tmp['inheritance']); + } + + $used_c = array_unique($used_c); + $decl_c = array_unique($decl_c); + $undecl_c = array_diff($used_c, $decl_c); + + return array('used_classes' => $used_c, + 'declared_classes' => $decl_c, + 'declared_methods' => $decl_m, + 'declared_functions' => $decl_f, + 'undeclared_classes' => $undecl_c, + 'inheritance' => $inheri, + ); + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir (optional) directory to save file in + * @param mixed $callback (optional) function/method to call for status + * updates + * + * @return string Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). + * + * @access public + * @deprecated in favor of PEAR_Downloader::downloadHttp() + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback); + } +} + +require_once 'PEAR/Config.php'; +require_once 'PEAR/PackageFile.php';
\ No newline at end of file diff --git a/includes/pear/PEAR/Config.php b/includes/pear/PEAR/Config.php new file mode 100644 index 0000000..f6fb9ee --- /dev/null +++ b/includes/pear/PEAR/Config.php @@ -0,0 +1,2092 @@ +<?php +/** + * PEAR_Config, customized configuration handling for the PEAR Installer + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Required for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Registry.php'; +require_once 'PEAR/Installer/Role.php'; +require_once 'System.php'; + +/** + * Last created PEAR_Config instance. + * @var object + */ +$GLOBALS['_PEAR_Config_instance'] = null; +if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) { + $PEAR_INSTALL_DIR = PHP_LIBDIR . DIRECTORY_SEPARATOR . 'pear'; +} else { + $PEAR_INSTALL_DIR = PEAR_INSTALL_DIR; +} + +// Below we define constants with default values for all configuration +// parameters except username/password. All of them can have their +// defaults set through environment variables. The reason we use the +// PHP_ prefix is for some security, PHP protects environment +// variables starting with PHP_*. + +// default channel and preferred mirror is based on whether we are invoked through +// the "pear" or the "pecl" command +if (!defined('PEAR_RUNTYPE')) { + define('PEAR_RUNTYPE', 'pear'); +} + +if (PEAR_RUNTYPE == 'pear') { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net'); +} else { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net'); +} + +if (getenv('PHP_PEAR_SYSCONF_DIR')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR')); +} elseif (getenv('SystemRoot')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('SystemRoot')); +} else { + define('PEAR_CONFIG_SYSCONFDIR', PHP_SYSCONFDIR); +} + +// Default for master_server +if (getenv('PHP_PEAR_MASTER_SERVER')) { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', getenv('PHP_PEAR_MASTER_SERVER')); +} else { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', 'pear.php.net'); +} + +// Default for http_proxy +if (getenv('PHP_PEAR_HTTP_PROXY')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('PHP_PEAR_HTTP_PROXY')); +} elseif (getenv('http_proxy')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('http_proxy')); +} else { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', ''); +} + +// Default for php_dir +if (getenv('PHP_PEAR_INSTALL_DIR')) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR')); +} else { + if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } +} + +// Default for ext_dir +if (getenv('PHP_PEAR_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR')); +} else { + if (ini_get('extension_dir')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir')); + } elseif (defined('PEAR_EXTENSION_DIR') && + file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR); + } elseif (defined('PHP_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', '.'); + } +} + +// Default for doc_dir +if (getenv('PHP_PEAR_DOC_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', getenv('PHP_PEAR_DOC_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'docs'); +} + +// Default for bin_dir +if (getenv('PHP_PEAR_BIN_DIR')) { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', getenv('PHP_PEAR_BIN_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', PHP_BINDIR); +} + +// Default for data_dir +if (getenv('PHP_PEAR_DATA_DIR')) { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', getenv('PHP_PEAR_DATA_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data'); +} + +// Default for cfg_dir +if (getenv('PHP_PEAR_CFG_DIR')) { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg'); +} + +// Default for www_dir +if (getenv('PHP_PEAR_WWW_DIR')) { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www'); +} + +// Default for test_dir +if (getenv('PHP_PEAR_TEST_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests'); +} + +// Default for temp_dir +if (getenv('PHP_PEAR_TEMP_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'temp'); +} + +// Default for cache_dir +if (getenv('PHP_PEAR_CACHE_DIR')) { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'cache'); +} + +// Default for download_dir +if (getenv('PHP_PEAR_DOWNLOAD_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'download'); +} + +// Default for php_bin +if (getenv('PHP_PEAR_PHP_BIN')) { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', PEAR_CONFIG_DEFAULT_BIN_DIR. + DIRECTORY_SEPARATOR.'php'.(OS_WINDOWS ? '.exe' : '')); +} + +// Default for verbose +if (getenv('PHP_PEAR_VERBOSE')) { + define('PEAR_CONFIG_DEFAULT_VERBOSE', getenv('PHP_PEAR_VERBOSE')); +} else { + define('PEAR_CONFIG_DEFAULT_VERBOSE', 1); +} + +// Default for preferred_state +if (getenv('PHP_PEAR_PREFERRED_STATE')) { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', getenv('PHP_PEAR_PREFERRED_STATE')); +} else { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', 'stable'); +} + +// Default for umask +if (getenv('PHP_PEAR_UMASK')) { + define('PEAR_CONFIG_DEFAULT_UMASK', getenv('PHP_PEAR_UMASK')); +} else { + define('PEAR_CONFIG_DEFAULT_UMASK', decoct(umask())); +} + +// Default for cache_ttl +if (getenv('PHP_PEAR_CACHE_TTL')) { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', getenv('PHP_PEAR_CACHE_TTL')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', 3600); +} + +// Default for sig_type +if (getenv('PHP_PEAR_SIG_TYPE')) { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', getenv('PHP_PEAR_SIG_TYPE')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', 'gpg'); +} + +// Default for sig_bin +if (getenv('PHP_PEAR_SIG_BIN')) { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', getenv('PHP_PEAR_SIG_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', + System::which( + 'gpg', OS_WINDOWS ? 'c:\gnupg\gpg.exe' : '/usr/local/bin/gpg')); +} + +// Default for sig_keydir +if (getenv('PHP_PEAR_SIG_KEYDIR')) { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', getenv('PHP_PEAR_SIG_KEYDIR')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', + PEAR_CONFIG_SYSCONFDIR . DIRECTORY_SEPARATOR . 'pearkeys'); +} + +/** + * This is a class for storing configuration data, keeping track of + * which are system-defined, user-defined or defaulted. + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Config extends PEAR +{ + /** + * Array of config files used. + * + * @var array layer => config file + */ + var $files = array( + 'system' => '', + 'user' => '', + ); + + var $layers = array(); + + /** + * Configuration data, two-dimensional array where the first + * dimension is the config layer ('user', 'system' and 'default'), + * and the second dimension is keyname => value. + * + * The order in the first dimension is important! Earlier + * layers will shadow later ones when a config value is + * requested (if a 'user' value exists, it will be returned first, + * then 'system' and finally 'default'). + * + * @var array layer => array(keyname => value, ...) + */ + var $configuration = array( + 'user' => array(), + 'system' => array(), + 'default' => array(), + ); + + /** + * Configuration values that can be set for a channel + * + * All other configuration values can only have a global value + * @var array + * @access private + */ + var $_channelConfigInfo = array( + 'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir', + 'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username', + 'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini' + ); + + /** + * Channels that can be accessed + * @see setChannels() + * @var array + * @access private + */ + var $_channels = array('pear.php.net', 'pecl.php.net', '__uri'); + + /** + * This variable is used to control the directory values returned + * @see setInstallRoot(); + * @var string|false + * @access private + */ + var $_installRoot = false; + + /** + * If requested, this will always refer to the registry + * contained in php_dir + * @var PEAR_Registry + */ + var $_registry = array(); + + /** + * @var array + * @access private + */ + var $_regInitialized = array(); + + /** + * @var bool + * @access private + */ + var $_noRegistry = false; + + /** + * amount of errors found while parsing config + * @var integer + * @access private + */ + var $_errorsFound = 0; + var $_lastError = null; + + /** + * Information about the configuration data. Stores the type, + * default value and a documentation string for each configuration + * value. + * + * @var array layer => array(infotype => value, ...) + */ + var $configuration_info = array( + // Channels/Internet Access + 'default_channel' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default channel to use for all non explicit commands', + 'prompt' => 'Default Channel', + 'group' => 'Internet Access', + ), + 'preferred_mirror' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default server or mirror to use for channel actions', + 'prompt' => 'Default Channel Mirror', + 'group' => 'Internet Access', + ), + 'remote_config' => array( + 'type' => 'password', + 'default' => '', + 'doc' => 'ftp url of remote configuration file to use for synchronized install', + 'prompt' => 'Remote Configuration File', + 'group' => 'Internet Access', + ), + 'auto_discover' => array( + 'type' => 'integer', + 'default' => 1, + 'doc' => 'whether to automatically discover new channels', + 'prompt' => 'Auto-discover new Channels', + 'group' => 'Internet Access', + ), + // Internet Access + 'master_server' => array( + 'type' => 'string', + 'default' => 'pear.php.net', + 'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]', + 'prompt' => 'PEAR server [DEPRECATED]', + 'group' => 'Internet Access', + ), + 'http_proxy' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_HTTP_PROXY, + 'doc' => 'HTTP proxy (host:port) to use when downloading packages', + 'prompt' => 'HTTP Proxy Server Address', + 'group' => 'Internet Access', + ), + // File Locations + 'php_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_PHP_DIR, + 'doc' => 'directory where .php files are installed', + 'prompt' => 'PEAR directory', + 'group' => 'File Locations', + ), + 'ext_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_EXT_DIR, + 'doc' => 'directory where loadable extensions are installed', + 'prompt' => 'PHP extension directory', + 'group' => 'File Locations', + ), + 'doc_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOC_DIR, + 'doc' => 'directory where documentation is installed', + 'prompt' => 'PEAR documentation directory', + 'group' => 'File Locations', + ), + 'bin_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_BIN_DIR, + 'doc' => 'directory where executables are installed', + 'prompt' => 'PEAR executables directory', + 'group' => 'File Locations', + ), + 'data_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DATA_DIR, + 'doc' => 'directory where data files are installed', + 'prompt' => 'PEAR data directory', + 'group' => 'File Locations (Advanced)', + ), + 'cfg_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CFG_DIR, + 'doc' => 'directory where modifiable configuration files are installed', + 'prompt' => 'PEAR configuration file directory', + 'group' => 'File Locations (Advanced)', + ), + 'www_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_WWW_DIR, + 'doc' => 'directory where www frontend files (html/js) are installed', + 'prompt' => 'PEAR www files directory', + 'group' => 'File Locations (Advanced)', + ), + 'test_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEST_DIR, + 'doc' => 'directory where regression tests are installed', + 'prompt' => 'PEAR test directory', + 'group' => 'File Locations (Advanced)', + ), + 'cache_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR, + 'doc' => 'directory which is used for web service cache', + 'prompt' => 'PEAR Installer cache directory', + 'group' => 'File Locations (Advanced)', + ), + 'temp_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR, + 'doc' => 'directory which is used for all temp files', + 'prompt' => 'PEAR Installer temp directory', + 'group' => 'File Locations (Advanced)', + ), + 'download_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR, + 'doc' => 'directory which is used for all downloaded files', + 'prompt' => 'PEAR Installer download directory', + 'group' => 'File Locations (Advanced)', + ), + 'php_bin' => array( + 'type' => 'file', + 'default' => PEAR_CONFIG_DEFAULT_PHP_BIN, + 'doc' => 'PHP CLI/CGI binary for executing scripts', + 'prompt' => 'PHP CLI/CGI binary', + 'group' => 'File Locations (Advanced)', + ), + 'php_prefix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-prefix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_suffix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-suffix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_ini' => array( + 'type' => 'file', + 'default' => '', + 'doc' => 'location of php.ini in which to enable PECL extensions on install', + 'prompt' => 'php.ini location', + 'group' => 'File Locations (Advanced)', + ), + // Maintainers + 'username' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '(maintainers) your PEAR account name', + 'prompt' => 'PEAR username (for maintainers)', + 'group' => 'Maintainers', + ), + 'password' => array( + 'type' => 'password', + 'default' => '', + 'doc' => '(maintainers) your PEAR account password', + 'prompt' => 'PEAR password (for maintainers)', + 'group' => 'Maintainers', + ), + // Advanced + 'verbose' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_VERBOSE, + 'doc' => 'verbosity level +0: really quiet +1: somewhat quiet +2: verbose +3: debug', + 'prompt' => 'Debug Log Level', + 'group' => 'Advanced', + ), + 'preferred_state' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_PREFERRED_STATE, + 'doc' => 'the installer will prefer releases with this state when installing packages without a version or state specified', + 'valid_set' => array( + 'stable', 'beta', 'alpha', 'devel', 'snapshot'), + 'prompt' => 'Preferred Package State', + 'group' => 'Advanced', + ), + 'umask' => array( + 'type' => 'mask', + 'default' => PEAR_CONFIG_DEFAULT_UMASK, + 'doc' => 'umask used when creating files (Unix-like systems only)', + 'prompt' => 'Unix file mask', + 'group' => 'Advanced', + ), + 'cache_ttl' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_TTL, + 'doc' => 'amount of secs where the local cache is used and not updated', + 'prompt' => 'Cache TimeToLive', + 'group' => 'Advanced', + ), + 'sig_type' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_SIG_TYPE, + 'doc' => 'which package signature mechanism to use', + 'valid_set' => array('gpg'), + 'prompt' => 'Package Signature Type', + 'group' => 'Maintainers', + ), + 'sig_bin' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_SIG_BIN, + 'doc' => 'which package signature mechanism to use', + 'prompt' => 'Signature Handling Program', + 'group' => 'Maintainers', + ), + 'sig_keyid' => array( + 'type' => 'string', + 'default' => '', + 'doc' => 'which key to use for signing with', + 'prompt' => 'Signature Key Id', + 'group' => 'Maintainers', + ), + 'sig_keydir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR, + 'doc' => 'directory where signature keys are located', + 'prompt' => 'Signature Key Directory', + 'group' => 'Maintainers', + ), + // __channels is reserved - used for channel-specific configuration + ); + + /** + * Constructor. + * + * @param string file to read user-defined options from + * @param string file to read system-wide defaults from + * @param bool determines whether a registry object "follows" + * the value of php_dir (is automatically created + * and moved when php_dir is changed) + * @param bool if true, fails if configuration files cannot be loaded + * + * @access public + * + * @see PEAR_Config::singleton + */ + function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, + $strict = true) + { + $this->PEAR(); + PEAR_Installer_Role::initializeConfig($this); + $sl = DIRECTORY_SEPARATOR; + if (empty($user_file)) { + if (OS_WINDOWS) { + $user_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini'; + } else { + $user_file = getenv('HOME') . $sl . '.pearrc'; + } + } + + if (empty($system_file)) { + $system_file = PEAR_CONFIG_SYSCONFDIR . $sl; + if (OS_WINDOWS) { + $system_file .= 'pearsys.ini'; + } else { + $system_file .= 'pear.conf'; + } + } + + $this->layers = array_keys($this->configuration); + $this->files['user'] = $user_file; + $this->files['system'] = $system_file; + if ($user_file && file_exists($user_file)) { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->readConfigFile($user_file, 'user', $strict); + $this->popErrorHandling(); + if ($this->_errorsFound > 0) { + return; + } + } + + if ($system_file && @file_exists($system_file)) { + $this->mergeConfigFile($system_file, false, 'system', $strict); + if ($this->_errorsFound > 0) { + return; + } + + } + + if (!$ftp_file) { + $ftp_file = $this->get('remote_config'); + } + + if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) { + $this->readFTPConfigFile($ftp_file); + } + + foreach ($this->configuration_info as $key => $info) { + $this->configuration['default'][$key] = $info['default']; + } + + $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']); + $this->_registry['default']->setConfig($this, false); + $this->_regInitialized['default'] = false; + //$GLOBALS['_PEAR_Config_instance'] = &$this; + } + + /** + * Return the default locations of user and system configuration files + * @static + */ + function getDefaultConfigFiles() + { + $sl = DIRECTORY_SEPARATOR; + if (OS_WINDOWS) { + return array( + 'user' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini' + ); + } + + return array( + 'user' => getenv('HOME') . $sl . '.pearrc', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf' + ); + } + + /** + * Static singleton method. If you want to keep only one instance + * of this class in use, this method will give you a reference to + * the last created PEAR_Config object if one exists, or create a + * new object. + * + * @param string (optional) file to read user-defined options from + * @param string (optional) file to read system-wide defaults from + * + * @return object an existing or new PEAR_Config instance + * + * @access public + * + * @see PEAR_Config::PEAR_Config + */ + function &singleton($user_file = '', $system_file = '', $strict = true) + { + if (is_object($GLOBALS['_PEAR_Config_instance'])) { + return $GLOBALS['_PEAR_Config_instance']; + } + + $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict); + if ($t_conf->_errorsFound > 0) { + return $t_conf->lastError; + } + + $GLOBALS['_PEAR_Config_instance'] = &$t_conf; + return $GLOBALS['_PEAR_Config_instance']; + } + + /** + * Determine whether any configuration files have been detected, and whether a + * registry object can be retrieved from this configuration. + * @return bool + * @since PEAR 1.4.0a1 + */ + function validConfiguration() + { + if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) { + return true; + } + + return false; + } + + /** + * Reads configuration data from a file. All existing values in + * the config layer are discarded and replaced with data from the + * file. + * @param string file to read from, if NULL or not specified, the + * last-used file for the same layer (second param) is used + * @param string config layer to insert data into ('user' or 'system') + * @return bool TRUE on success or a PEAR error on failure + */ + function readConfigFile($file = null, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->files[$layer] = $file; + $this->_decodeInput($data); + $this->configuration[$layer] = $data; + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini + * @return true|PEAR_Error + */ + function readFTPConfigFile($path) + { + do { // poor man's try + if (!class_exists('PEAR_FTP')) { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (PEAR_Common::isIncludeable('PEAR/FTP.php')) { + require_once 'PEAR/FTP.php'; + } + } + + if (!class_exists('PEAR_FTP')) { + return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config'); + } + + $this->_ftp = &new PEAR_FTP; + $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN); + $e = $this->_ftp->init($path); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + $tmp = System::mktemp('-d'); + PEAR_Common::addTempFile($tmp); + $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR . + 'pear.ini', false, FTP_BINARY); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini'); + $this->_ftp->disconnect(); + $this->_ftp->popErrorHandling(); + $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini'; + $e = $this->readConfigFile(null, 'ftp'); + if (PEAR::isError($e)) { + return $e; + } + + $fail = array(); + foreach ($this->configuration_info as $key => $val) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + // any directory configs must be set for this to work + if (!isset($this->configuration['ftp'][$key])) { + $fail[] = $key; + } + } + } + + if (!count($fail)) { + return true; + } + + $fail = '"' . implode('", "', $fail) . '"'; + unset($this->files['ftp']); + unset($this->configuration['ftp']); + return PEAR::raiseError('ERROR: Ftp configuration file must set all ' . + 'directory configuration variables. These variables were not set: ' . + $fail); + } while (false); // poor man's catch + unset($this->files['ftp']); + return PEAR::raiseError('no remote host specified'); + } + + /** + * Reads the existing configurations and creates the _channels array from it + */ + function _setupChannels() + { + $set = array_flip(array_values($this->_channels)); + foreach ($this->configuration as $layer => $data) { + $i = 1000; + if (isset($data['__channels']) && is_array($data['__channels'])) { + foreach ($data['__channels'] as $channel => $info) { + $set[$channel] = $i++; + } + } + } + $this->_channels = array_values(array_flip($set)); + $this->setChannels($this->_channels); + } + + function deleteChannel($channel) + { + $ch = strtolower($channel); + foreach ($this->configuration as $layer => $data) { + if (isset($data['__channels']) && isset($data['__channels'][$ch])) { + unset($this->configuration[$layer]['__channels'][$ch]); + } + } + + $this->_channels = array_flip($this->_channels); + unset($this->_channels[$ch]); + $this->_channels = array_flip($this->_channels); + } + + /** + * Merges data into a config layer from a file. Does the same + * thing as readConfigFile, except it does not replace all + * existing values in the config layer. + * @param string file to read from + * @param bool whether to overwrite existing data (default TRUE) + * @param string config layer to insert data into ('user' or 'system') + * @param string if true, errors are returned if file opening fails + * @return bool TRUE on success or a PEAR error on failure + */ + function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->_decodeInput($data); + if ($override) { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data); + } else { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]); + } + + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param array + * @param array + * @return array + * @static + */ + function arrayMergeRecursive($arr2, $arr1) + { + $ret = array(); + foreach ($arr2 as $key => $data) { + if (!isset($arr1[$key])) { + $ret[$key] = $data; + unset($arr1[$key]); + continue; + } + if (is_array($data)) { + if (!is_array($arr1[$key])) { + $ret[$key] = $arr1[$key]; + unset($arr1[$key]); + continue; + } + $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]); + unset($arr1[$key]); + } + } + + return array_merge($ret, $arr1); + } + + /** + * Writes data into a config layer from a file. + * + * @param string|null file to read from, or null for default + * @param string config layer to insert data into ('user' or + * 'system') + * @param string|null data to write to config file or null for internal data [DEPRECATED] + * @return bool TRUE on success or a PEAR error on failure + */ + function writeConfigFile($file = null, $layer = 'user', $data = null) + { + $this->_lazyChannelSetup($layer); + if ($layer == 'both' || $layer == 'all') { + foreach ($this->files as $type => $file) { + $err = $this->writeConfigFile($file, $type, $data); + if (PEAR::isError($err)) { + return $err; + } + } + return true; + } + + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config file type `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = ($data === null) ? $this->configuration[$layer] : $data; + $this->_encodeOutput($data); + $opt = array('-p', dirname($file)); + if (!@System::mkDir($opt)) { + return $this->raiseError("could not create directory: " . dirname($file)); + } + + if (file_exists($file) && is_file($file) && !is_writeable($file)) { + return $this->raiseError("no write access to $file!"); + } + + $fp = @fopen($file, "w"); + if (!$fp) { + return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)"); + } + + $contents = "#PEAR_Config 0.9\n" . serialize($data); + if (!@fwrite($fp, $contents)) { + return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)"); + } + return true; + } + + /** + * Reads configuration data from a file and returns the parsed data + * in an array. + * + * @param string file to read from + * @return array configuration data or a PEAR error on failure + */ + private function _readConfigDataFrom($file) + { + $fp = false; + if (file_exists($file)) { + $fp = @fopen($file, "r"); + } + + if (!$fp) { + return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed"); + } + + $size = filesize($file); + fclose($fp); + $contents = file_get_contents($file); + if (empty($contents)) { + return $this->raiseError('Configuration file "' . $file . '" is empty'); + } + + $version = false; + if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) { + $version = $matches[1]; + $contents = substr($contents, strlen($matches[0])); + } else { + // Museum config file + if (substr($contents,0,2) == 'a:') { + $version = '0.1'; + } + } + + if ($version && version_compare("$version", '1', '<')) { + // no '@', it is possible that unserialize + // raises a notice but it seems to block IO to + // STDOUT if a '@' is used and a notice is raise + $data = unserialize($contents); + + if (!is_array($data) && !$data) { + if ($contents == serialize(false)) { + $data = array(); + } else { + $err = $this->raiseError("PEAR_Config: bad data in $file"); + return $err; + } + } + if (!is_array($data)) { + if (strlen(trim($contents)) > 0) { + $error = "PEAR_Config: bad data in $file"; + $err = $this->raiseError($error); + return $err; + } + + $data = array(); + } + // add parsing of newer formats here... + } else { + $err = $this->raiseError("$file: unknown version `$version'"); + return $err; + } + + return $data; + } + + /** + * Gets the file used for storing the config for a layer + * + * @param string $layer 'user' or 'system' + */ + function getConfFile($layer) + { + return $this->files[$layer]; + } + + /** + * @param string Configuration class name, used for detecting duplicate calls + * @param array information on a role as parsed from its xml file + * @return true|PEAR_Error + * @access private + */ + function _addConfigVars($class, $vars) + { + static $called = array(); + if (isset($called[$class])) { + return; + } + + $called[$class] = 1; + if (count($vars) > 3) { + return $this->raiseError('Roles can only define 3 new config variables or less'); + } + + foreach ($vars as $name => $var) { + if (!is_array($var)) { + return $this->raiseError('Configuration information must be an array'); + } + + if (!isset($var['type'])) { + return $this->raiseError('Configuration information must contain a type'); + } elseif (!in_array($var['type'], + array('string', 'mask', 'password', 'directory', 'file', 'set'))) { + return $this->raiseError( + 'Configuration type must be one of directory, file, string, ' . + 'mask, set, or password'); + } + if (!isset($var['default'])) { + return $this->raiseError( + 'Configuration information must contain a default value ("default" index)'); + } + + if (is_array($var['default'])) { + $real_default = ''; + foreach ($var['default'] as $config_var => $val) { + if (strpos($config_var, 'text') === 0) { + $real_default .= $val; + } elseif (strpos($config_var, 'constant') === 0) { + if (!defined($val)) { + return $this->raiseError( + 'Unknown constant "' . $val . '" requested in ' . + 'default value for configuration variable "' . + $name . '"'); + } + + $real_default .= constant($val); + } elseif (isset($this->configuration_info[$config_var])) { + $real_default .= + $this->configuration_info[$config_var]['default']; + } else { + return $this->raiseError( + 'Unknown request for "' . $config_var . '" value in ' . + 'default value for configuration variable "' . + $name . '"'); + } + } + $var['default'] = $real_default; + } + + if ($var['type'] == 'integer') { + $var['default'] = (integer) $var['default']; + } + + if (!isset($var['doc'])) { + return $this->raiseError( + 'Configuration information must contain a summary ("doc" index)'); + } + + if (!isset($var['prompt'])) { + return $this->raiseError( + 'Configuration information must contain a simple prompt ("prompt" index)'); + } + + if (!isset($var['group'])) { + return $this->raiseError( + 'Configuration information must contain a simple group ("group" index)'); + } + + if (isset($this->configuration_info[$name])) { + return $this->raiseError('Configuration variable "' . $name . + '" already exists'); + } + + $this->configuration_info[$name] = $var; + // fix bug #7351: setting custom config variable in a channel fails + $this->_channelConfigInfo[] = $name; + } + + return true; + } + + /** + * Encodes/scrambles configuration data before writing to files. + * Currently, 'password' values will be base64-encoded as to avoid + * that people spot cleartext passwords by accident. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + */ + function _encodeOutput(&$data) + { + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_encodeOutput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + // we base64-encode passwords so they are at least + // not shown in plain by accident + case 'password': { + $data[$key] = base64_encode($data[$key]); + break; + } + case 'mask': { + $data[$key] = octdec($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Decodes/unscrambles configuration data after reading from files. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + * + * @see PEAR_Config::_encodeOutput + */ + function _decodeInput(&$data) + { + if (!is_array($data)) { + return true; + } + + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_decodeInput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + case 'password': { + $data[$key] = base64_decode($data[$key]); + break; + } + case 'mask': { + $data[$key] = decoct($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Retrieve the default channel. + * + * On startup, channels are not initialized, so if the default channel is not + * pear.php.net, then initialize the config. + * @param string registry layer + * @return string|false + */ + function getDefaultChannel($layer = null) + { + $ret = false; + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + break; + } + } + } elseif (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + } + + if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') { + $ret = 'pecl.php.net'; + } + + if ($ret) { + if ($ret != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + + return $ret; + } + + return PEAR_CONFIG_DEFAULT_CHANNEL; + } + + /** + * Returns a configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access public + */ + function get($key, $layer = null, $channel = false) + { + if (!isset($this->configuration_info[$key])) { + return null; + } + + if ($key == '__channels') { + return null; + } + + if ($key == 'default_channel') { + return $this->getDefaultChannel($layer); + } + + if (!$channel) { + $channel = $this->getDefaultChannel(); + } elseif ($channel != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + $channel = strtolower($channel); + + $test = (in_array($key, $this->_channelConfigInfo)) ? + $this->_getChannelValue($key, $layer, $channel) : + null; + if ($test !== null) { + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + return $test; + } + + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + return $test; + } + } + } elseif (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + + return $test; + } + + return null; + } + + /** + * Returns a channel-specific configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access private + */ + function _getChannelValue($key, $layer, $channel) + { + if ($key == '__channels' || $channel == 'pear.php.net') { + return null; + } + + $ret = null; + if ($layer === null) { + foreach ($this->layers as $ilayer) { + if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$ilayer]['__channels'][$channel][$key]; + break; + } + } + } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$layer]['__channels'][$channel][$key]; + } + + if ($key != 'preferred_mirror') { + return $ret; + } + + + if ($ret !== null) { + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($ret) && $chan->getName() != $ret) { + return $channel; // mirror does not exist + } + } + + return $ret; + } + + if ($channel != $this->getDefaultChannel($layer)) { + return $channel; // we must use the channel name as the preferred mirror + // if the user has not chosen an alternate + } + + return $this->getDefaultChannel($layer); + } + + /** + * Set a config value in a specific layer (defaults to 'user'). + * Enforces the types defined in the configuration_info array. An + * integer config variable will be cast to int, and a set config + * variable will be validated against its legal values. + * + * @param string config key + * @param string config value + * @param string (optional) config layer + * @param string channel to set this value for, or null for global value + * @return bool TRUE on success, FALSE on failure + */ + function set($key, $value, $layer = 'user', $channel = false) + { + if ($key == '__channels') { + return false; + } + + if (!isset($this->configuration[$layer])) { + return false; + } + + if ($key == 'default_channel') { + // can only set this value globally + $channel = 'pear.php.net'; + if ($value != 'pear.php.net') { + $this->_lazyChannelSetup($layer); + } + } + + if ($key == 'preferred_mirror') { + if ($channel == '__uri') { + return false; // can't set the __uri pseudo-channel's mirror + } + + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net'); + if (PEAR::isError($chan)) { + return false; + } + + if (!$chan->getMirror($value) && $chan->getName() != $value) { + return false; // mirror does not exist + } + } + } + + if (!isset($this->configuration_info[$key])) { + return false; + } + + extract($this->configuration_info[$key]); + switch ($type) { + case 'integer': + $value = (int)$value; + break; + case 'set': { + // If a valid_set is specified, require the value to + // be in the set. If there is no valid_set, accept + // any value. + if ($valid_set) { + reset($valid_set); + if ((key($valid_set) === 0 && !in_array($value, $valid_set)) || + (key($valid_set) !== 0 && empty($valid_set[$value]))) + { + return false; + } + } + break; + } + } + + if (!$channel) { + $channel = $this->get('default_channel', null, 'pear.php.net'); + } + + if (!in_array($channel, $this->_channels)) { + $this->_lazyChannelSetup($layer); + $reg = &$this->getRegistry($layer); + if ($reg) { + $channel = $reg->channelName($channel); + } + + if (!in_array($channel, $this->_channels)) { + return false; + } + } + + if ($channel != 'pear.php.net') { + if (in_array($key, $this->_channelConfigInfo)) { + $this->configuration[$layer]['__channels'][$channel][$key] = $value; + return true; + } + + return false; + } + + if ($key == 'default_channel') { + if (!isset($reg)) { + $reg = &$this->getRegistry($layer); + if (!$reg) { + $reg = &$this->getRegistry(); + } + } + + if ($reg) { + $value = $reg->channelName($value); + } + + if (!$value) { + return false; + } + } + + $this->configuration[$layer][$key] = $value; + if ($key == 'php_dir' && !$this->_noRegistry) { + if (!isset($this->_registry[$layer]) || + $value != $this->_registry[$layer]->install_dir) { + $this->_registry[$layer] = &new PEAR_Registry($value); + $this->_regInitialized[$layer] = false; + $this->_registry[$layer]->setConfig($this, false); + } + } + + return true; + } + + function _lazyChannelSetup($uselayer = false) + { + if ($this->_noRegistry) { + return; + } + + $merge = false; + foreach ($this->_registry as $layer => $p) { + if ($uselayer && $uselayer != $layer) { + continue; + } + + if (!$this->_regInitialized[$layer]) { + if ($layer == 'default' && isset($this->_registry['user']) || + isset($this->_registry['system'])) { + // only use the default registry if there are no alternatives + continue; + } + + if (!is_object($this->_registry[$layer])) { + if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + return; + } + } + + $this->setChannels($this->_registry[$layer]->listChannels(), $merge); + $this->_regInitialized[$layer] = true; + $merge = true; + } + } + } + + /** + * Set the list of channels. + * + * This should be set via a call to {@link PEAR_Registry::listChannels()} + * @param array + * @param bool + * @return bool success of operation + */ + function setChannels($channels, $merge = false) + { + if (!is_array($channels)) { + return false; + } + + if ($merge) { + $this->_channels = array_merge($this->_channels, $channels); + } else { + $this->_channels = $channels; + } + + foreach ($channels as $channel) { + $channel = strtolower($channel); + if ($channel == 'pear.php.net') { + continue; + } + + foreach ($this->layers as $layer) { + if (!isset($this->configuration[$layer]['__channels'])) { + $this->configuration[$layer]['__channels'] = array(); + } + if (!isset($this->configuration[$layer]['__channels'][$channel]) + || !is_array($this->configuration[$layer]['__channels'][$channel])) { + $this->configuration[$layer]['__channels'][$channel] = array(); + } + } + } + + return true; + } + + /** + * Get the type of a config value. + * + * @param string config key + * + * @return string type, one of "string", "integer", "file", + * "directory", "set" or "password". + * + * @access public + * + */ + function getType($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['type']; + } + return false; + } + + /** + * Get the documentation for a config value. + * + * @param string config key + * @return string documentation string + * + * @access public + * + */ + function getDocs($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['doc']; + } + + return false; + } + + /** + * Get the short documentation for a config value. + * + * @param string config key + * @return string short documentation string + * + * @access public + * + */ + function getPrompt($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['prompt']; + } + + return false; + } + + /** + * Get the parameter group for a config key. + * + * @param string config key + * @return string parameter group + * + * @access public + * + */ + function getGroup($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['group']; + } + + return false; + } + + /** + * Get the list of parameter groups. + * + * @return array list of parameter groups + * + * @access public + * + */ + function getGroups() + { + $tmp = array(); + foreach ($this->configuration_info as $key => $info) { + $tmp[$info['group']] = 1; + } + + return array_keys($tmp); + } + + /** + * Get the list of the parameters in a group. + * + * @param string $group parameter group + * @return array list of parameters in $group + * + * @access public + * + */ + function getGroupKeys($group) + { + $keys = array(); + foreach ($this->configuration_info as $key => $info) { + if ($info['group'] == $group) { + $keys[] = $key; + } + } + + return $keys; + } + + /** + * Get the list of allowed set values for a config value. Returns + * NULL for config values that are not sets. + * + * @param string config key + * @return array enumerated array of set values, or NULL if the + * config key is unknown or not a set + * + * @access public + * + */ + function getSetValues($key) + { + if (isset($this->configuration_info[$key]) && + isset($this->configuration_info[$key]['type']) && + $this->configuration_info[$key]['type'] == 'set') + { + $valid_set = $this->configuration_info[$key]['valid_set']; + reset($valid_set); + if (key($valid_set) === 0) { + return $valid_set; + } + + return array_keys($valid_set); + } + + return null; + } + + /** + * Get all the current config keys. + * + * @return array simple array of config keys + * + * @access public + */ + function getKeys() + { + $keys = array(); + foreach ($this->layers as $layer) { + $test = $this->configuration[$layer]; + if (isset($test['__channels'])) { + foreach ($test['__channels'] as $channel => $configs) { + $keys = array_merge($keys, $configs); + } + } + + unset($test['__channels']); + $keys = array_merge($keys, $test); + + } + return array_keys($keys); + } + + /** + * Remove the a config key from a specific config layer. + * + * @param string config key + * @param string (optional) config layer + * @param string (optional) channel (defaults to default channel) + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function remove($key, $layer = 'user', $channel = null) + { + if ($channel === null) { + $channel = $this->getDefaultChannel(); + } + + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + unset($this->configuration[$layer]['__channels'][$channel][$key]); + return true; + } + } + + if (isset($this->configuration[$layer][$key])) { + unset($this->configuration[$layer][$key]); + return true; + } + + return false; + } + + /** + * Temporarily remove an entire config layer. USE WITH CARE! + * + * @param string config key + * @param string (optional) config layer + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function removeLayer($layer) + { + if (isset($this->configuration[$layer])) { + $this->configuration[$layer] = array(); + return true; + } + + return false; + } + + /** + * Stores configuration data in a layer. + * + * @param string config layer to store + * @return bool TRUE on success, or PEAR error on failure + * + * @access public + */ + function store($layer = 'user', $data = null) + { + return $this->writeConfigFile(null, $layer, $data); + } + + /** + * Tells what config layer that gets to define a key. + * + * @param string config key + * @param boolean return the defining channel + * + * @return string|array the config layer, or an empty string if not found. + * + * if $returnchannel, the return is an array array('layer' => layername, + * 'channel' => channelname), or an empty string if not found + * + * @access public + */ + function definedBy($key, $returnchannel = false) + { + foreach ($this->layers as $layer) { + $channel = $this->getDefaultChannel(); + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => $channel); + } + return $layer; + } + } + + if (isset($this->configuration[$layer][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => 'pear.php.net'); + } + return $layer; + } + } + + return ''; + } + + /** + * Tells whether a given key exists as a config value. + * + * @param string config key + * @return bool whether <config key> exists in this object + * + * @access public + */ + function isDefined($key) + { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + return true; + } + } + + return false; + } + + /** + * Tells whether a given config layer exists. + * + * @param string config layer + * @return bool whether <config layer> exists in this object + * + * @access public + */ + function isDefinedLayer($layer) + { + return isset($this->configuration[$layer]); + } + + /** + * Returns the layers defined (except the 'default' one) + * + * @return array of the defined layers + */ + function getLayers() + { + $cf = $this->configuration; + unset($cf['default']); + return array_keys($cf); + } + + function apiVersion() + { + return '1.1'; + } + + /** + * @return PEAR_Registry + */ + function &getRegistry($use = null) + { + $layer = $use === null ? 'user' : $use; + if (isset($this->_registry[$layer])) { + return $this->_registry[$layer]; + } elseif ($use === null && isset($this->_registry['system'])) { + return $this->_registry['system']; + } elseif ($use === null && isset($this->_registry['default'])) { + return $this->_registry['default']; + } elseif ($use) { + $a = false; + return $a; + } + + // only go here if null was passed in + echo "CRITICAL ERROR: Registry could not be initialized from any value"; + exit(1); + } + + /** + * This is to allow customization like the use of installroot + * @param PEAR_Registry + * @return bool + */ + function setRegistry(&$reg, $layer = 'user') + { + if ($this->_noRegistry) { + return false; + } + + if (!in_array($layer, array('user', 'system'))) { + return false; + } + + $this->_registry[$layer] = &$reg; + if (is_object($reg)) { + $this->_registry[$layer]->setConfig($this, false); + } + + return true; + } + + function noRegistry() + { + $this->_noRegistry = true; + } + + /** + * @return PEAR_REST + */ + function &getREST($version, $options = array()) + { + $version = str_replace('.', '', $version); + if (!class_exists($class = 'PEAR_REST_' . $version)) { + require_once 'PEAR/REST/' . $version . '.php'; + } + + $remote = &new $class($this, $options); + return $remote; + } + + /** + * The ftp server is set in {@link readFTPConfigFile()}. It exists only if a + * remote configuration file has been specified + * @return PEAR_FTP|false + */ + function &getFTP() + { + if (isset($this->_ftp)) { + return $this->_ftp; + } + + $a = false; + return $a; + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string|false installation directory to prepend to all _dir variables, or false to + * disable + */ + function setInstallRoot($root) + { + if (substr($root, -1) == DIRECTORY_SEPARATOR) { + $root = substr($root, 0, -1); + } + $old = $this->_installRoot; + $this->_installRoot = $root; + if (($old != $root) && !$this->_noRegistry) { + foreach (array_keys($this->_registry) as $layer) { + if ($layer == 'ftp' || !isset($this->_registry[$layer])) { + continue; + } + $this->_registry[$layer] = + &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net')); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } + } + } +} diff --git a/includes/pear/PEAR/Dependency2.php b/includes/pear/PEAR/Dependency2.php new file mode 100644 index 0000000..6d78964 --- /dev/null +++ b/includes/pear/PEAR/Dependency2.php @@ -0,0 +1,1362 @@ +<?php +/** + * PEAR_Dependency2, advanced dependency validation + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Required for the PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; + +/** + * Dependency check for PEAR packages + * + * This class handles both version 1.0 and 2.0 dependencies + * WARNING: *any* changes to this class must be duplicated in the + * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc, + * or unit tests will not actually validate the changes + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Dependency2 +{ + /** + * One of the PEAR_VALIDATE_* states + * @see PEAR_VALIDATE_NORMAL + * @var integer + */ + var $_state; + + /** + * Command-line options to install/upgrade/uninstall commands + * @param array + */ + var $_options; + + /** + * @var OS_Guess + */ + var $_os; + + /** + * @var PEAR_Registry + */ + var $_registry; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencydb; + + /** + * Output of PEAR_Registry::parsedPackageName() + * @var array + */ + var $_currentPackage; + + /** + * @param PEAR_Config + * @param array installation options + * @param array format of PEAR_Registry::parsedPackageName() + * @param int installation state (one of PEAR_VALIDATE_*) + */ + function PEAR_Dependency2(&$config, $installoptions, $package, + $state = PEAR_VALIDATE_INSTALLING) + { + $this->_config = &$config; + if (!class_exists('PEAR_DependencyDB')) { + require_once 'PEAR/DependencyDB.php'; + } + + if (isset($installoptions['packagingroot'])) { + // make sure depdb is in the right location + $config->setInstallRoot($installoptions['packagingroot']); + } + + $this->_registry = &$config->getRegistry(); + $this->_dependencydb = &PEAR_DependencyDB::singleton($config); + if (isset($installoptions['packagingroot'])) { + $config->setInstallRoot(false); + } + + $this->_options = $installoptions; + $this->_state = $state; + if (!class_exists('OS_Guess')) { + require_once 'OS/Guess.php'; + } + + $this->_os = new OS_Guess; + $this->_currentPackage = $package; + } + + function _getExtraString($dep) + { + $extra = ' ('; + if (isset($dep['uri'])) { + return ''; + } + + if (isset($dep['recommended'])) { + $extra .= 'recommended version ' . $dep['recommended']; + } else { + if (isset($dep['min'])) { + $extra .= 'version >= ' . $dep['min']; + } + + if (isset($dep['max'])) { + if ($extra != ' (') { + $extra .= ', '; + } + $extra .= 'version <= ' . $dep['max']; + } + + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if ($extra != ' (') { + $extra .= ', '; + } + + $extra .= 'excluded versions: '; + foreach ($dep['exclude'] as $i => $exclude) { + if ($i) { + $extra .= ', '; + } + $extra .= $exclude; + } + } + } + + $extra .= ')'; + if ($extra == ' ()') { + $extra = ''; + } + + return $extra; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPHP_OS() + { + return PHP_OS; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getsysname() + { + return $this->_os->getSysname(); + } + + /** + * Specify a dependency on an OS. Use arch for detailed os/processor information + * + * There are two generic OS dependencies that will be the most common, unix and windows. + * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix + */ + function validateOsDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if ($dep['name'] == '*') { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + switch (strtolower($dep['name'])) { + case 'windows' : + if ($not) { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on Windows"); + } + + return $this->warning("warning: Cannot install %s on Windows"); + } + } else { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on Windows"); + } + + return $this->warning("warning: Can only install %s on Windows"); + } + } + break; + case 'unix' : + $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix'); + if ($not) { + if (in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on any Unix system"); + } + + return $this->warning( "warning: Cannot install %s on any Unix system"); + } + } else { + if (!in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on a Unix system"); + } + + return $this->warning("warning: Can only install %s on a Unix system"); + } + } + break; + default : + if ($not) { + if (strtolower($dep['name']) == strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . $dep['name'] . + ' operating system'); + } + + return $this->warning('warning: Cannot install %s on ' . + $dep['name'] . ' operating system'); + } + } else { + if (strtolower($dep['name']) != strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + + return $this->warning('warning: Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + } + } + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function matchSignature($pattern) + { + return $this->_os->matchSignature($pattern); + } + + /** + * Specify a complex dependency on an OS/processor/kernel version, + * Use OS for simple operating system dependency. + * + * This is the only dependency that accepts an eregable pattern. The pattern + * will be matched against the php_uname() output parsed by OS_Guess + */ + function validateArchDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING) { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + if (!$this->matchSignature($dep['pattern'])) { + if (!$not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, does not ' . + 'match "' . $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, does ' . + 'not match "' . $dep['pattern'] . '"'); + } + + return true; + } + + if ($not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, required "' . + $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, ' . + 'required "' . $dep['pattern'] . '"'); + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function extension_loaded($name) + { + return extension_loaded($name); + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function phpversion($name = null) + { + if ($name !== null) { + return phpversion($name); + } + + return phpversion(); + } + + function validateExtensionDependency($dep, $required = true) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $loaded = $this->extension_loaded($dep['name']); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($loaded) { + if (isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!$loaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if (!$required) { + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + $version = (string) $this->phpversion($dep['name']); + if (empty($version)) { + $version = '0'; + } + + $fail = false; + if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (isset($dep['conflicts'])) { + continue; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + } + } + + if (isset($dep['recommended'])) { + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] . + ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'] . + '", but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency: PHP extension ' . + $dep['name'] . ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'].'"'); + } + + return true; + } + + function validatePhpDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $version = $this->phpversion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (isset($dep['min'])) { + if (!version_compare($version, $dep['min'], '>=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['max'])) { + if (!version_compare($version, $dep['max'], '<=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP version ' . + $exclude); + } + + return $this->warning( + 'warning: %s is not compatible with PHP version ' . + $exclude); + } + } + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPEARVersion() + { + return '1.9.4'; + } + + function validatePearinstallerDependency($dep) + { + $pearversion = $this->getPEARVersion(); + if (array_key_exists('PEAR_RUNTESTS_PEAR_VERSION_RETURN', $GLOBALS)) { + // A means for testing pear-core in local repositories. + return $GLOBALS['PEAR_RUNTESTS_PEAR_VERSION_RETURN']; + } + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (version_compare($pearversion, $dep['min'], '<')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + if (isset($dep['max'])) { + if (version_compare($pearversion, $dep['max'], '>')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + } + + if (isset($dep['exclude'])) { + if (!isset($dep['exclude'][0])) { + $dep['exclude'] = array($dep['exclude']); + } + + foreach ($dep['exclude'] as $exclude) { + if (version_compare($exclude, $pearversion, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PEAR Installer ' . + 'version ' . $exclude); + } + + return $this->warning('warning: %s is not compatible with PEAR ' . + 'Installer version ' . $exclude); + } + } + } + + return true; + } + + function validateSubpackageDependency($dep, $required, $params) + { + return $this->validatePackageDependency($dep, $required, $params); + } + + /** + * @param array dependency information (2.0 format) + * @param boolean whether this is a required dependency + * @param array a list of downloaded packages to be installed, if any + * @param boolean if true, then deps on pear.php.net that fail will also check + * against pecl.php.net packages to accomodate extensions that have + * moved to pecl.php.net from pear.php.net + */ + function validatePackageDependency($dep, $required, $params, $depv1 = false) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if (isset($dep['providesextension'])) { + if ($this->extension_loaded($dep['providesextension'])) { + $save = $dep; + $subdep = $dep; + $subdep['name'] = $subdep['providesextension']; + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->validateExtensionDependency($subdep, $required); + PEAR::popErrorHandling(); + if (!PEAR::isError($ret)) { + return true; + } + } + } + + if ($this->_state == PEAR_VALIDATE_INSTALLING) { + return $this->_validatePackageInstall($dep, $required, $depv1); + } + + if ($this->_state == PEAR_VALIDATE_DOWNLOADING) { + return $this->_validatePackageDownload($dep, $required, $params, $depv1); + } + } + + function _validatePackageDownload($dep, $required, $params, $depv1 = false) + { + $dep['package'] = $dep['name']; + if (isset($dep['uri'])) { + $dep['channel'] = '__uri'; + } + + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $found = false; + foreach ($params as $param) { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => $dep['channel']))) { + $found = true; + break; + } + + if ($depv1 && $dep['channel'] == 'pear.php.net') { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => 'pecl.php.net'))) { + $found = true; + break; + } + } + } + + if (!$found && isset($dep['providesextension'])) { + foreach ($params as $param) { + if ($param->isExtension($dep['providesextension'])) { + $found = true; + break; + } + } + } + + if ($found) { + $version = $param->getVersion(); + $installed = false; + $downloaded = true; + } else { + if ($this->_registry->packageExists($dep['name'], $dep['channel'])) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + $dep['channel']); + } else { + if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'], + 'pear.php.net')) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + 'pear.php.net'); + } else { + $version = 'not installed or downloaded'; + $installed = false; + $downloaded = false; + } + } + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($installed || $downloaded) { + $installed = $installed ? 'installed' : 'downloaded'; + if (isset($dep['conflicts'])) { + $rest = ''; + if ($version) { + $rest = ", $installed version is " . $version; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + if (!$installed && !$downloaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + $dep['package'] = $dep['name']; + $dep = $this->_registry->parsedPackageNameToString($dep, true); + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && + isset($dep['conflicts']) && !isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . + ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + if (isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force']) + ) { + return $this->raiseError('%s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + } + } + + if (isset($dep['recommended'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!$found && $installed) { + $param = $this->_registry->getPackage($dep['name'], $dep['channel']); + } + + if ($param) { + $found = false; + foreach ($params as $parent) { + if ($parent->isEqual($this->_currentPackage)) { + $found = true; + break; + } + } + + if ($found) { + if ($param->isCompatible($parent)) { + return true; + } + } else { // this is for validPackage() calls + $parent = $this->_registry->getPackage($this->_currentPackage['package'], + $this->_currentPackage['channel']); + if ($parent !== null && $param->isCompatible($parent)) { + return true; + } + } + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) && + !isset($this->_options['loose']) + ) { + return $this->raiseError('%s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended'] . + ', but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended']); + } + + return true; + } + + function _validatePackageInstall($dep, $required, $depv1 = false) + { + return $this->_validatePackageDownload($dep, $required, array(), $depv1); + } + + /** + * Verify that uninstalling packages passed in to command line is OK. + * + * @param PEAR_Installer $dl + * @return PEAR_Error|true + */ + function validatePackageUninstall(&$dl) + { + if (PEAR::isError($this->_dependencydb)) { + return $this->_dependencydb; + } + + $params = array(); + // construct an array of "downloaded" packages to fool the package dependency checker + // into using these to validate uninstalls of circular dependencies + $downloaded = &$dl->getUninstallPackages(); + foreach ($downloaded as $i => $pf) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $dp = &new PEAR_Downloader_Package($dl); + $dp->setPackageFile($downloaded[$i]); + $params[$i] = &$dp; + } + + // check cache + $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' . + strtolower($this->_currentPackage['package']); + if (isset($dl->___uninstall_package_cache)) { + $badpackages = $dl->___uninstall_package_cache; + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + + return true; + } + + // first, list the immediate parents of each package to be uninstalled + $perpackagelist = array(); + $allparents = array(); + foreach ($params as $i => $param) { + $a = array( + 'channel' => strtolower($param->getChannel()), + 'package' => strtolower($param->getPackage()) + ); + + $deps = $this->_dependencydb->getDependentPackages($a); + if ($deps) { + foreach ($deps as $d) { + $pardeps = $this->_dependencydb->getDependencies($d); + foreach ($pardeps as $dep) { + if (strtolower($dep['dep']['channel']) == $a['channel'] && + strtolower($dep['dep']['name']) == $a['package']) { + if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) { + $perpackagelist[$a['channel'] . '/' . $a['package']] = array(); + } + $perpackagelist[$a['channel'] . '/' . $a['package']][] + = array($d['channel'] . '/' . $d['package'], $dep); + if (!isset($allparents[$d['channel'] . '/' . $d['package']])) { + $allparents[$d['channel'] . '/' . $d['package']] = array(); + } + if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) { + $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array(); + } + $allparents[$d['channel'] . '/' . $d['package']] + [$a['channel'] . '/' . $a['package']][] + = array($d, $dep); + } + } + } + } + } + + // next, remove any packages from the parents list that are not installed + $remove = array(); + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) { + continue; + } + $remove[$parent] = true; + } + } + + // next remove any packages from the parents list that are not passed in for + // uninstallation + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + foreach ($params as $param) { + if (strtolower($param->getChannel()) == $d[0][0]['channel'] && + strtolower($param->getPackage()) == $d[0][0]['package']) { + // found it + continue 3; + } + } + $remove[$parent] = true; + } + } + + // remove all packages whose dependencies fail + // save which ones failed for error reporting + $badchildren = array(); + do { + $fail = false; + foreach ($remove as $package => $unused) { + if (!isset($allparents[$package])) { + continue; + } + + foreach ($allparents[$package] as $kid => $d1) { + foreach ($d1 as $depinfo) { + if ($depinfo[1]['type'] != 'optional') { + if (isset($badchildren[$kid])) { + continue; + } + $badchildren[$kid] = true; + $remove[$kid] = true; + $fail = true; + continue 2; + } + } + } + if ($fail) { + // start over, we removed some children + continue 2; + } + } + } while ($fail); + + // next, construct the list of packages that can't be uninstalled + $badpackages = array(); + $save = $this->_currentPackage; + foreach ($perpackagelist as $package => $packagedeps) { + foreach ($packagedeps as $parent) { + if (!isset($remove[$parent[0]])) { + continue; + } + + $packagename = $this->_registry->parsePackageName($parent[0]); + $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']); + $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']); + $packagename['package'] = $pa->getPackage(); + $this->_currentPackage = $packagename; + // parent is not present in uninstall list, make sure we can actually + // uninstall it (parent dep is optional) + $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']); + $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']); + $parentname['package'] = $pa->getPackage(); + $parent[1]['dep']['package'] = $parentname['package']; + $parent[1]['dep']['channel'] = $parentname['channel']; + if ($parent[1]['type'] == 'optional') { + $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl); + if ($test !== true) { + $badpackages[$package]['warnings'][] = $test; + } + } else { + $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl); + if ($test !== true) { + $badpackages[$package]['errors'][] = $test; + } + } + } + } + + $this->_currentPackage = $save; + $dl->___uninstall_package_cache = $badpackages; + if (isset($badpackages[$memyselfandI])) { + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + } + + return true; + } + + function _validatePackageUninstall($dep, $required, $dl) + { + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']); + if (!$version) { + return true; + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (isset($dep['conflicts'])) { + return true; // uninstall OK - these packages conflict (probably installed with --force) + } + + if (!isset($dep['min']) && !isset($dep['max'])) { + if (!$required) { + return $this->warning('"' . $depname . '" can be optionally used by ' . + 'installed package %s' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('"' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + return $this->warning('warning: "' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + // we re-use this variable, preserve the original value + $saverequired = $required; + if (!$required) { + return $this->warning($depname . $extra . ' can be optionally used by installed package' . + ' "%s"'); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError($depname . $extra . ' is required by installed package' . + ' "%s"'); + } + + return $this->raiseError('warning: ' . $depname . $extra . + ' is required by installed package "%s"'); + } + + /** + * validate a downloaded package against installed packages + * + * As of PEAR 1.4.3, this will only validate + * + * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * $pkg package identifier (either + * array('package' => blah, 'channel' => blah) or an array with + * index 'info' referencing an object) + * @param PEAR_Downloader $dl + * @param array $params full list of packages to install + * @return true|PEAR_Error + */ + function validatePackage($pkg, &$dl, $params = array()) + { + if (is_array($pkg) && isset($pkg['info'])) { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']); + } else { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg); + } + + $fail = false; + if ($deps) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + + $dp = &new PEAR_Downloader_Package($dl); + if (is_object($pkg)) { + $dp->setPackageFile($pkg); + } else { + $dp->setDownloadURL($pkg); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($deps as $channel => $info) { + foreach ($info as $package => $ds) { + foreach ($params as $packd) { + if (strtolower($packd->getPackage()) == strtolower($package) && + $packd->getChannel() == $channel) { + $dl->log(3, 'skipping installed package check of "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $channel, 'package' => $package), + true) . + '", version "' . $packd->getVersion() . '" will be ' . + 'downloaded and installed'); + continue 2; // jump to next package + } + } + + foreach ($ds as $d) { + $checker = &new PEAR_Dependency2($this->_config, $this->_options, + array('channel' => $channel, 'package' => $package), $this->_state); + $dep = $d['dep']; + $required = $d['type'] == 'required'; + $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp)); + if (is_array($ret)) { + $dl->log(0, $ret[0]); + } elseif (PEAR::isError($ret)) { + $dl->log(0, $ret->getMessage()); + $fail = true; + } + } + } + } + PEAR::popErrorHandling(); + } + + if ($fail) { + return $this->raiseError( + '%s cannot be installed, conflicts with installed packages'); + } + + return true; + } + + /** + * validate a package.xml 1.0 dependency + */ + function validateDependency1($dep, $params = array()) + { + if (!isset($dep['optional'])) { + $dep['optional'] = 'no'; + } + + list($newdep, $type) = $this->normalizeDep($dep); + if (!$newdep) { + return $this->raiseError("Invalid Dependency"); + } + + if (method_exists($this, "validate{$type}Dependency")) { + return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no', + $params, true); + } + } + + /** + * Convert a 1.0 dep into a 2.0 dep + */ + function normalizeDep($dep) + { + $types = array( + 'pkg' => 'Package', + 'ext' => 'Extension', + 'os' => 'Os', + 'php' => 'Php' + ); + + if (!isset($types[$dep['type']])) { + return array(false, false); + } + + $type = $types[$dep['type']]; + + $newdep = array(); + switch ($type) { + case 'Package' : + $newdep['channel'] = 'pear.php.net'; + case 'Extension' : + case 'Os' : + $newdep['name'] = $dep['name']; + break; + } + + $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']); + switch ($dep['rel']) { + case 'has' : + return array($newdep, $type); + break; + case 'not' : + $newdep['conflicts'] = true; + break; + case '>=' : + case '>' : + $newdep['min'] = $dep['version']; + if ($dep['rel'] == '>') { + $newdep['exclude'] = $dep['version']; + } + break; + case '<=' : + case '<' : + $newdep['max'] = $dep['version']; + if ($dep['rel'] == '<') { + $newdep['exclude'] = $dep['version']; + } + break; + case 'ne' : + case '!=' : + $newdep['min'] = '0'; + $newdep['max'] = '100000'; + $newdep['exclude'] = $dep['version']; + break; + case '==' : + $newdep['min'] = $dep['version']; + $newdep['max'] = $dep['version']; + break; + } + if ($type == 'Php') { + if (!isset($newdep['min'])) { + $newdep['min'] = '4.4.0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '6.0.0'; + } + } + return array($newdep, $type); + } + + /** + * Converts text comparing operators to them sign equivalents + * + * Example: 'ge' to '>=' + * + * @access public + * @param string Operator + * @return string Sign equivalent + */ + function signOperator($operator) + { + switch($operator) { + case 'lt': return '<'; + case 'le': return '<='; + case 'gt': return '>'; + case 'ge': return '>='; + case 'eq': return '=='; + case 'ne': return '!='; + default: + return $operator; + } + } + + function raiseError($msg) + { + if (isset($this->_options['ignore-errors'])) { + return $this->warning($msg); + } + + return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } + + function warning($msg) + { + return array(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/DependencyDB.php b/includes/pear/PEAR/DependencyDB.php new file mode 100644 index 0000000..e3ad0ec --- /dev/null +++ b/includes/pear/PEAR/DependencyDB.php @@ -0,0 +1,757 @@ +<?php +/** + * PEAR_DependencyDB, advanced installed packages dependency database + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Config.php'; + +$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array(); +/** + * Track dependency relationships between installed packages + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @author Tomas V.V.Cox <cox@idec.net.com> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_DependencyDB +{ + // {{{ properties + + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Config + */ + private $_config; + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Registry + */ + private $_registry; + /** + * Filename of the dependency DB (usually .depdb) + * @var string + */ + private $_depdb = false; + /** + * File name of the lockfile (usually .depdblock) + * @var string + */ + private $_lockfile = false; + /** + * Open file resource for locking the lockfile + * @var resource|false + */ + private $_lockFp = false; + /** + * API version of this class, used to validate a file on-disk + * @var string + */ + private $_version = '1.0'; + /** + * Cached dependency database file + * @var array|null + */ + private $_cache; + + // }}} + // {{{ & singleton() + + /** + * Get a raw dependency database. Calls setConfig() and assertDepsDB() + * @param PEAR_Config + * @param string|false full path to the dependency database, or false to use default + * @return PEAR_DependencyDB|PEAR_Error + */ + public static function &singleton(&$config, $depdb = false) + { + $phpdir = $config->get('php_dir', null, 'pear.php.net'); + if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) { + $a = new PEAR_DependencyDB; + $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a; + $a->setConfig($config, $depdb); + $e = $a->assertDepsDB(); + if (PEAR::isError($e)) { + return $e; + } + } + + return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir]; + } + + /** + * Set up the registry/location of dependency DB + * @param PEAR_Config|false + * @param string|false full path to the dependency database, or false to use default + */ + public function setConfig(&$config, $depdb = false) + { + if (!$config) { + $this->_config = &PEAR_Config::singleton(); + } else { + $this->_config = &$config; + } + + $this->_registry = &$this->_config->getRegistry(); + if (!$depdb) { + $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'; + } else { + $this->_depdb = $depdb; + } + + $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock'; + } + // }}} + + public function hasWriteAccess() + { + if (!file_exists($this->_depdb)) { + $dir = $this->_depdb; + while ($dir && $dir != '.') { + $dir = dirname($dir); // cd .. + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + } + + return false; + } + + return is_writeable($this->_depdb); + } + + // {{{ assertDepsDB() + + /** + * Create the dependency database, if it doesn't exist. Error if the database is + * newer than the code reading it. + * @return void|PEAR_Error + */ + public function assertDepsDB() + { + if (!is_file($this->_depdb)) { + $this->rebuildDB(); + return; + } + + $depdb = $this->_getDepDB(); + // Datatype format has been changed, rebuild the Deps DB + if ($depdb['_version'] < $this->_version) { + $this->rebuildDB(); + } + + if ($depdb['_version']{0} > $this->_version{0}) { + return PEAR::raiseError('Dependency database is version ' . + $depdb['_version'] . ', and we are version ' . + $this->_version . ', cannot continue'); + } + } + + /** + * Get a list of installed packages that depend on this package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + public function getDependentPackages(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (isset($data['packages'][$channel][$package])) { + return $data['packages'][$channel][$package]; + } + + return false; + } + + /** + * Get a list of the actual dependencies of installed packages that depend on + * a package. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + public function getDependentPackageDependencies(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $depend = $this->getDependentPackages($pkg); + if (!$depend) { + return false; + } + + $dependencies = array(); + foreach ($depend as $info) { + $temp = $this->getDependencies($info); + foreach ($temp as $dep) { + if ( + isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) && + strtolower($dep['dep']['channel']) == $channel && + strtolower($dep['dep']['name']) == $package + ) { + if (!isset($dependencies[$info['channel']])) { + $dependencies[$info['channel']] = array(); + } + + if (!isset($dependencies[$info['channel']][$info['package']])) { + $dependencies[$info['channel']][$info['package']] = array(); + } + $dependencies[$info['channel']][$info['package']][] = $dep; + } + } + } + + return $dependencies; + } + + /** + * Get a list of dependencies of this installed package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + public function getDependencies(&$pkg) + { + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $data = $this->_getDepDB(); + if (isset($data['dependencies'][$channel][$package])) { + return $data['dependencies'][$channel][$package]; + } + + return false; + } + + /** + * Determine whether $parent depends on $child, near or deep + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + public function dependsOn($parent, $child) + { + $c = array(); + $this->_getDepDB(); + return $this->_dependsOn($parent, $child, $c); + } + + private function _dependsOn($parent, $child, &$checked) + { + if (is_object($parent)) { + $channel = strtolower($parent->getChannel()); + $package = strtolower($parent->getPackage()); + } else { + $channel = strtolower($parent['channel']); + $package = strtolower($parent['package']); + } + + if (is_object($child)) { + $depchannel = strtolower($child->getChannel()); + $deppackage = strtolower($child->getPackage()); + } else { + $depchannel = strtolower($child['channel']); + $deppackage = strtolower($child['package']); + } + + if (isset($checked[$channel][$package][$depchannel][$deppackage])) { + return false; // avoid endless recursion + } + + $checked[$channel][$package][$depchannel][$deppackage] = true; + if (!isset($this->_cache['dependencies'][$channel][$package])) { + return false; + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if (is_object($child)) { + if ($info['dep']['uri'] == $child->getURI()) { + return true; + } + } elseif (isset($child['uri'])) { + if ($info['dep']['uri'] == $child['uri']) { + return true; + } + } + return false; + } + + if (strtolower($info['dep']['channel']) == $depchannel && + strtolower($info['dep']['name']) == $deppackage) { + return true; + } + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if ($this->_dependsOn(array( + 'uri' => $info['dep']['uri'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } else { + if ($this->_dependsOn(array( + 'channel' => $info['dep']['channel'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } + } + + return false; + } + + /** + * Register dependencies of a package that is being installed or upgraded + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + public function installPackage(&$package) + { + $data = $this->_getDepDB(); + unset($this->_cache); + $this->_setPackageDeps($data, $package); + $this->_writeDepDB($data); + } + + /** + * Remove dependencies of a package that is being uninstalled, or upgraded. + * + * Upgraded packages first uninstall, then install + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have + * indices 'channel' and 'package' + */ + public function uninstallPackage(&$pkg) + { + $data = $this->_getDepDB(); + unset($this->_cache); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (!isset($data['dependencies'][$channel][$package])) { + return true; + } + + foreach ($data['dependencies'][$channel][$package] as $dep) { + $found = false; + $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']); + $depname = strtolower($dep['dep']['name']); + if (isset($data['packages'][$depchannel][$depname])) { + foreach ($data['packages'][$depchannel][$depname] as $i => $info) { + if ($info['channel'] == $channel && $info['package'] == $package) { + $found = true; + break; + } + } + } + + if ($found) { + unset($data['packages'][$depchannel][$depname][$i]); + if (!count($data['packages'][$depchannel][$depname])) { + unset($data['packages'][$depchannel][$depname]); + if (!count($data['packages'][$depchannel])) { + unset($data['packages'][$depchannel]); + } + } else { + $data['packages'][$depchannel][$depname] = + array_values($data['packages'][$depchannel][$depname]); + } + } + } + + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + + if (!count($data['dependencies'])) { + unset($data['dependencies']); + } + + if (!count($data['packages'])) { + unset($data['packages']); + } + + $this->_writeDepDB($data); + } + + /** + * Rebuild the dependency DB by reading registry entries. + * @return true|PEAR_Error + */ + public function rebuildDB() + { + $depdb = array('_version' => $this->_version); + if (!$this->hasWriteAccess()) { + // allow startup for read-only with older Registry + return $depdb; + } + + $packages = $this->_registry->listAllPackages(); + if (PEAR::isError($packages)) { + return $packages; + } + + foreach ($packages as $channel => $ps) { + foreach ($ps as $package) { + $package = $this->_registry->getPackage($package, $channel); + if (PEAR::isError($package)) { + return $package; + } + $this->_setPackageDeps($depdb, $package); + } + } + + $error = $this->_writeDepDB($depdb); + if (PEAR::isError($error)) { + return $error; + } + + $this->_cache = $depdb; + return true; + } + + /** + * Register usage of the dependency DB to prevent race conditions + * @param int one of the LOCK_* constants + * @return true|PEAR_Error + */ + private function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->_lockFp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH) { + if (!file_exists($this->_lockfile)) { + touch($this->_lockfile); + } elseif (!is_file($this->_lockfile)) { + return PEAR::raiseError('could not create Dependency lock file, ' . + 'it exists and is not a regular file'); + } + $open_mode = 'r'; + } + + if (!is_resource($this->_lockFp)) { + $this->_lockFp = @fopen($this->_lockfile, $open_mode); + } + + if (!is_resource($this->_lockFp)) { + return PEAR::raiseError("could not create Dependency lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->_lockFp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)"); + } + + return true; + } + + /** + * Release usage of dependency DB + * @return true|PEAR_Error + */ + private function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->_lockFp)) { + fclose($this->_lockFp); + } + $this->_lockFp = null; + return $ret; + } + + /** + * Load the dependency database from disk, or return the cache + * @return array|PEAR_Error + */ + private function _getDepDB() + { + if (!$this->hasWriteAccess()) { + return array('_version' => $this->_version); + } + + if (isset($this->_cache)) { + return $this->_cache; + } + + if (!$fp = fopen($this->_depdb, 'r')) { + $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'"); + return $err; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + fclose($fp); + $data = unserialize(file_get_contents($this->_depdb)); + set_magic_quotes_runtime($rt); + $this->_cache = $data; + return $data; + } + + /** + * Write out the dependency database to disk + * @param array the database + * @return true|PEAR_Error + */ + private function _writeDepDB(&$deps) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + if (!$fp = fopen($this->_depdb, 'wb')) { + $this->_unlock(); + return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fwrite($fp, serialize($deps)); + set_magic_quotes_runtime($rt); + fclose($fp); + $this->_unlock(); + $this->_cache = $deps; + return true; + } + + /** + * Register all dependencies from a package in the dependencies database, in essence + * "installing" the package's dependency information + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + private function _setPackageDeps(&$data, &$pkg) + { + $pkg->setConfig($this->_config); + if ($pkg->getPackagexmlVersion() == '1.0') { + $gen = &$pkg->getDefaultGenerator(); + $deps = $gen->dependenciesToV2(); + } else { + $deps = $pkg->getDeps(true); + } + + if (!$deps) { + return; + } + + if (!is_array($data)) { + $data = array(); + } + + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + $data['dependencies'][$channel][$package] = array(); + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['required']['subpackage'])) { + if (!isset($deps['required']['subpackage'][0])) { + $deps['required']['subpackage'] = array($deps['required']['subpackage']); + } + + foreach ($deps['required']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['subpackage'])) { + if (!isset($deps['optional']['subpackage'][0])) { + $deps['optional']['subpackage'] = array($deps['optional']['subpackage']); + } + + foreach ($deps['optional']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + + foreach ($group['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + + foreach ($group['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + } + } + + if ($data['dependencies'][$channel][$package] == array()) { + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + } + } + + /** + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array the specific dependency + * @param required|optional whether this is a required or an optional dep + * @param string|false dependency group this dependency is from, or false for ordinary dep + */ + private function _registerDep(&$data, &$pkg, $dep, $type, $group = false) + { + $info = array( + 'dep' => $dep, + 'type' => $type, + 'group' => $group + ); + + $dep = array_map('strtolower', $dep); + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + if (!isset($data['dependencies'][$channel][$package])) { + $data['dependencies'][$channel][$package] = array(); + } + + $data['dependencies'][$channel][$package][] = $info; + if (isset($data['packages'][$depchannel][$dep['name']])) { + $found = false; + foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) { + if ($p['channel'] == $channel && $p['package'] == $package) { + $found = true; + break; + } + } + } else { + if (!isset($data['packages'])) { + $data['packages'] = array(); + } + + if (!isset($data['packages'][$depchannel])) { + $data['packages'][$depchannel] = array(); + } + + if (!isset($data['packages'][$depchannel][$dep['name']])) { + $data['packages'][$depchannel][$dep['name']] = array(); + } + + $found = false; + } + + if (!$found) { + $data['packages'][$depchannel][$dep['name']][] = array( + 'channel' => $channel, + 'package' => $package + ); + } + } +} diff --git a/includes/pear/PEAR/Downloader.php b/includes/pear/PEAR/Downloader.php new file mode 100644 index 0000000..b42a210 --- /dev/null +++ b/includes/pear/PEAR/Downloader.php @@ -0,0 +1,1766 @@ +<?php +/** + * PEAR_Downloader, the PEAR Installer's download utility class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Martin Jansen <mj@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.0 + */ + +/** + * Needed for constants, extending + */ +require_once 'PEAR/Common.php'; + +define('PEAR_INSTALLER_OK', 1); +define('PEAR_INSTALLER_FAILED', 0); +define('PEAR_INSTALLER_SKIPPED', -1); +define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2); + +/** + * Administration class used to download anything from the internet (PEAR Packages, + * static URLs, xml files) + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Martin Jansen <mj@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.0 + */ +class PEAR_Downloader extends PEAR_Common +{ + /** + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * Preferred Installation State (snapshot, devel, alpha, beta, stable) + * @var string|null + * @access private + */ + var $_preferredState; + + /** + * Options from command-line passed to Install. + * + * Recognized options:<br /> + * - onlyreqdeps : install all required dependencies as well + * - alldeps : install all dependencies, including optional + * - installroot : base relative path to install files in + * - force : force a download even if warnings would prevent it + * - nocompress : download uncompressed tarballs + * @see PEAR_Command_Install + * @access private + * @var array + */ + var $_options; + + /** + * Downloaded Packages after a call to download(). + * + * Format of each entry: + * + * <code> + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * </code> + * @access private + * @var array + */ + var $_downloadedPackages = array(); + + /** + * Packages slated for download. + * + * This is used to prevent downloading a package more than once should it be a dependency + * for two packages to be installed. + * Format of each entry: + * + * <pre> + * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml, + * ); + * </pre> + * @access private + * @var array + */ + var $_toDownload = array(); + + /** + * Array of every package installed, with names lower-cased. + * + * Format: + * <code> + * array('package1' => 0, 'package2' => 1, ); + * </code> + * @var array + */ + var $_installed = array(); + + /** + * @var array + * @access private + */ + var $_errorStack = array(); + + /** + * @var boolean + * @access private + */ + var $_internalDownload = false; + + /** + * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()} + * @var array + * @access private + */ + var $_packageSortTree; + + /** + * Temporary directory, or configuration value where downloads will occur + * @var string + */ + var $_downloadDir; + + /** + * @param PEAR_Frontend_* + * @param array + * @param PEAR_Config + */ + function PEAR_Downloader(&$ui, $options, &$config) + { + parent::PEAR_Common(); + $this->_options = $options; + $this->config = &$config; + $this->_preferredState = $this->config->get('preferred_state'); + $this->ui = &$ui; + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + + if (isset($this->_options['installroot'])) { + $this->config->setInstallRoot($this->_options['installroot']); + } + $this->_registry = &$config->getRegistry(); + + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + $this->_installed = $this->_registry->listAllPackages(); + foreach ($this->_installed as $key => $unused) { + if (!count($unused)) { + continue; + } + $strtolower = create_function('$a','return strtolower($a);'); + array_walk($this->_installed[$key], $strtolower); + } + } + } + + /** + * Attempt to discover a channel's remote capabilities from + * its server name + * @param string + * @return boolean + */ + function discover($channel) + { + $this->log(1, 'Attempting to discover channel "' . $channel . '"...'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $callback = $this->ui ? array(&$this, '_downloadCallback') : null; + if (!class_exists('System')) { + require_once 'System.php'; + } + + $tmpdir = $this->config->get('temp_dir'); + $tmp = System::mktemp('-d -t "' . $tmpdir . '"'); + $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + // Attempt to fallback to https automatically. + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...'); + $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + return false; + } + } + + list($a, $lastmodified) = $a; + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $b = new PEAR_ChannelFile; + if ($b->fromXmlFile($a)) { + unlink($a); + if ($this->config->get('auto_discover')) { + $this->_registry->addChannel($b, $lastmodified); + $alias = $b->getName(); + if ($b->getName() == $this->_registry->channelName($b->getAlias())) { + $alias = $b->getAlias(); + } + + $this->log(1, 'Auto-discovered channel "' . $channel . + '", alias "' . $alias . '", adding to registry'); + } + + return true; + } + + unlink($a); + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Downloader + * @return PEAR_Downloader_Package + */ + function &newDownloaderPackage(&$t) + { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $a = &new PEAR_Downloader_Package($t); + return $a; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param array + * @param array + * @param int + */ + function &getDependency2Object(&$c, $i, $p, $s) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $i, $p, $s); + return $z; + } + + function &download($params) + { + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channelschecked = array(); + // convert all parameters into PEAR_Downloader_Package objects + foreach ($params as $i => $param) { + $params[$i] = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $params[$i]->initialize($param); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + continue; + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft']) && $err->getMessage() !== '') { + $this->log(0, $err->getMessage()); + } + + $params[$i] = false; + if (is_object($param)) { + $param = $param->getChannel() . '/' . $param->getPackage(); + } + + if (!isset($this->_options['soft'])) { + $this->log(2, 'Package "' . $param . '" is not valid'); + } + + // Message logged above in a specific verbose mode, passing null to not show up on CLI + $this->pushError(null, PEAR_INSTALLER_SKIPPED); + } else { + do { + if ($params[$i] && $params[$i]->getType() == 'local') { + // bug #7090 skip channel.xml check for local packages + break; + } + + if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) && + !isset($this->_options['offline']) + ) { + $channelschecked[$params[$i]->getChannel()] = true; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('System')) { + require_once 'System.php'; + } + + $curchannel = &$this->_registry->getChannel($params[$i]->getChannel()); + if (PEAR::isError($curchannel)) { + PEAR::staticPopErrorHandling(); + return $this->raiseError($curchannel); + } + + if (PEAR::isError($dir = $this->getDownloadDir())) { + PEAR::staticPopErrorHandling(); + break; + } + + $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel()); + $url = 'http://' . $mirror . '/channel.xml'; + $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + // Attempt fallback to https automatically + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $a = $this->downloadHttp('https://' . $mirror . + '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + break; + } + } + $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() . + '" to update'); + } + } while (false); + + if ($params[$i] && !isset($this->_options['downloadonly'])) { + if (isset($this->_options['packagingroot'])) { + $checkdir = $this->_prependPath( + $this->config->get('php_dir', null, $params[$i]->getChannel()), + $this->_options['packagingroot']); + } else { + $checkdir = $this->config->get('php_dir', + null, $params[$i]->getChannel()); + } + + while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) { + $checkdir = dirname($checkdir); + } + + if ($checkdir == '.') { + $checkdir = '/'; + } + + if (!is_writeable($checkdir)) { + return PEAR::raiseError('Cannot install, php_dir for channel "' . + $params[$i]->getChannel() . '" is not writeable by the current user'); + } + } + } + } + + unset($channelschecked); + PEAR_Downloader_Package::removeDuplicates($params); + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) { + $reverify = true; + while ($reverify) { + $reverify = false; + foreach ($params as $i => $param) { + //PHP Bug 40768 / PEAR Bug #10944 + //Nested foreaches fail in PHP 5.2.1 + key($params); + $ret = $params[$i]->detectDependencies($params); + if (PEAR::isError($ret)) { + $reverify = true; + $params[$i] = false; + PEAR_Downloader_Package::removeDuplicates($params); + if (!isset($this->_options['soft'])) { + $this->log(0, $ret->getMessage()); + } + continue 2; + } + } + } + } + + if (isset($this->_options['offline'])) { + $this->log(3, 'Skipping dependency download check, --offline specified'); + } + + if (!count($params)) { + $a = array(); + return $a; + } + + while (PEAR_Downloader_Package::mergeDependencies($params)); + PEAR_Downloader_Package::removeDuplicates($params, true); + $errorparams = array(); + if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) { + if (count($errorparams)) { + foreach ($errorparams as $param) { + $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage()); + $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED); + } + $a = array(); + return $a; + } + } + + PEAR_Downloader_Package::removeInstalled($params); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($params); + PEAR::popErrorHandling(); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + $ret = array(); + $newparams = array(); + if (isset($this->_options['pretend'])) { + return $params; + } + + $somefailed = false; + foreach ($params as $i => $package) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$params[$i]->download(); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (!isset($this->_options['soft'])) { + $this->log(1, $pf->getMessage()); + $this->log(0, 'Error: cannot download "' . + $this->_registry->parsedPackageNameToString($package->getParsedPackage(), + true) . + '"'); + } + $somefailed = true; + continue; + } + + $newparams[] = &$params[$i]; + $ret[] = array( + 'file' => $pf->getArchiveFile(), + 'info' => &$pf, + 'pkg' => $pf->getPackage() + ); + } + + if ($somefailed) { + // remove params that did not download successfully + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($newparams, true); + PEAR::popErrorHandling(); + if (!count($newparams)) { + $this->pushError('Download failed', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + } + + $this->_downloadedPackages = $ret; + return $newparams; + } + + /** + * @param array all packages to be installed + */ + function analyzeDependencies(&$params, $force = false) + { + if (isset($this->_options['downloadonly'])) { + return; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $redo = true; + $reset = $hasfailed = $failed = false; + while ($redo) { + $redo = false; + foreach ($params as $i => $param) { + $deps = $param->getDeps(); + if (!$deps) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + continue; + } + + if (!$reset && $param->alreadyValidated() && !$force) { + continue; + } + + if (count($deps)) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + if ($send === null) { + $send = $param->getDownloadURL(); + } + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + + $failed = false; + if (isset($deps['required']) && is_array($deps['required'])) { + foreach ($deps['required'] as $type => $dep) { + // note: Dependency2 will never return a PEAR_Error if ignore-errors + // is specified, so soft is needed to turn off logging + if (!isset($dep[0])) { + if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + + if (isset($deps['optional']) && is_array($deps['optional'])) { + foreach ($deps['optional'] as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + + $groupname = $param->getGroup(); + if (isset($deps['group']) && $groupname) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + $found = false; + foreach ($deps['group'] as $group) { + if ($group['attribs']['name'] == $groupname) { + $found = true; + break; + } + } + + if ($found) { + unset($group['attribs']); + foreach ($group as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + } + } else { + foreach ($deps as $dep) { + if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + $params[$i]->setValidated(); + } + + if ($failed) { + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + } + } + + PEAR::staticPopErrorHandling(); + if ($hasfailed && (isset($this->_options['ignore-errors']) || + isset($this->_options['nodeps']))) { + // this is probably not needed, but just in case + if (!isset($this->_options['soft'])) { + $this->log(0, 'WARNING: dependencies failed'); + } + } + } + + /** + * Retrieve the directory that downloads will happen in + * @access private + * @return string + */ + function getDownloadDir() + { + if (isset($this->_downloadDir)) { + return $this->_downloadDir; + } + + $downloaddir = $this->config->get('download_dir'); + if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) { + if (is_dir($downloaddir) && !is_writable($downloaddir)) { + $this->log(0, 'WARNING: configuration download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir to avoid this warning'); + } + + if (!class_exists('System')) { + require_once 'System.php'; + } + + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $this->log(3, '+ tmp dir created at ' . $downloaddir); + } + + if (!is_writable($downloaddir)) { + if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) || + !is_writable($downloaddir)) { + return PEAR::raiseError('download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + + return $this->_downloadDir = $downloaddir; + } + + function setDownloadDir($dir) + { + if (!@is_writable($dir)) { + if (PEAR::isError(System::mkdir(array('-p', $dir)))) { + return PEAR::raiseError('download directory "' . $dir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + $this->_downloadDir = $dir; + } + + function configSet($key, $value, $layer = 'user', $channel = false) + { + $this->config->set($key, $value, $layer, $channel); + $this->_preferredState = $this->config->get('preferred_state', null, $channel); + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + } + + function setOptions($options) + { + $this->_options = $options; + } + + function getOptions() + { + return $this->_options; + } + + + /** + * @param array output of {@link parsePackageName()} + * @access private + */ + function _getPackageDownloadUrl($parr) + { + $curchannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $parr['channel']); + // getDownloadURL returns an array. On error, it only contains information + // on the latest release as array(version, info). On success it contains + // array(version, info, download url string) + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (!$this->_registry->channelExists($parr['channel'])) { + do { + if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) { + break; + } + + $this->configSet('default_channel', $curchannel); + return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']); + } while (false); + } + + $chan = &$this->_registry->getChannel($parr['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']); + $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']); + // package is installed - use the installed release stability level + if (!isset($parr['state']) && $stability !== null) { + $state = $stability['release']; + } + PEAR::staticPopErrorHandling(); + $base2 = false; + + $preferred_mirror = $this->config->get('preferred_mirror'); + if (!$chan->supportsREST($preferred_mirror) || + ( + !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror)) + && + !($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + if ($base2) { + $rest = &$this->config->getREST('1.3', $this->_options); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', $this->_options); + } + + $downloadVersion = false; + if (!isset($parr['version']) && !isset($parr['state']) && $version + && !PEAR::isError($version) + && !isset($this->_options['downloadonly']) + ) { + $downloadVersion = $version; + } + + $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName()); + if (PEAR::isError($url)) { + $this->configSet('default_channel', $curchannel); + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (!isset($this->_options['force']) && + !isset($this->_options['downloadonly']) && + $version && + !PEAR::isError($version) && + !isset($parr['group']) + ) { + if (version_compare($version, $url['version'], '=')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is the same as the ' . + 'released version ' . $url['version'], -976); + } + + if (version_compare($version, $url['version'], '>')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is newer than detected ' . + 'released version ' . $url['version'], -976); + } + } + + if (isset($url['info']['required']) || $url['compatible']) { + require_once 'PEAR/PackageFile/v2.php'; + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($parr['channel']); + if ($url['compatible']) { + $pf->setRawCompatible($url['compatible']); + } + } else { + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + } + + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + /** + * @param array dependency array + * @access private + */ + function _getDepPackageDownloadUrl($dep, $parr) + { + $xsdversion = isset($dep['rel']) ? '1.0' : '2.0'; + $curchannel = $this->config->get('default_channel'); + if (isset($dep['uri'])) { + $xsdversion = '2.0'; + $chan = &$this->_registry->getChannel('__uri'); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri'); + $this->configSet('default_channel', '__uri'); + } else { + if (isset($dep['channel'])) { + $remotechannel = $dep['channel']; + } else { + $remotechannel = 'pear.php.net'; + } + + if (!$this->_registry->channelExists($remotechannel)) { + do { + if ($this->config->get('auto_discover')) { + if ($this->discover($remotechannel)) { + break; + } + } + return PEAR::raiseError('Unknown remote channel: ' . $remotechannel); + } while (false); + } + + $chan = &$this->_registry->getChannel($remotechannel); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel); + $this->configSet('default_channel', $remotechannel); + } + + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (isset($parr['state']) && isset($parr['version'])) { + unset($parr['state']); + } + + if (isset($dep['uri'])) { + $info = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $info->initialize($dep); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + return PEAR::raiseError('Cannot initialize dependency'); + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $err->getMessage()); + } + + if (is_object($info)) { + $param = $info->getChannel() . '/' . $info->getPackage(); + } + return PEAR::raiseError('Package "' . $param . '" is not valid'); + } + return $info; + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) + && + ( + ($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror'))) + || + ($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) + ) + ) { + if ($base2) { + $base = $base2; + $rest = &$this->config->getREST('1.3', $this->_options); + } else { + $rest = &$this->config->getREST('1.0', $this->_options); + } + + $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr, + $state, $version, $chan->getName()); + if (PEAR::isError($url)) { + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (isset($url['info']['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($remotechannel); + } else { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + + } + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + /** + * @deprecated in favor of _getPackageDownloadUrl + */ + function getPackageDownloadUrl($package, $version = null, $channel = false) + { + if ($version) { + $package .= "-$version"; + } + if ($this === null || $this->_registry === null) { + $package = "http://pear.php.net/get/$package"; + } else { + $chan = $this->_registry->getChannel($channel); + if (PEAR::isError($chan)) { + return ''; + } + $package = "http://" . $chan->getServer() . "/get/$package"; + } + if (!extension_loaded("zlib")) { + $package .= '?uncompress=yes'; + } + return $package; + } + + /** + * Retrieve a list of downloaded packages after a call to {@link download()}. + * + * Also resets the list of downloaded packages. + * @return array + */ + function getDownloadedPackages() + { + $ret = $this->_downloadedPackages; + $this->_downloadedPackages = array(); + $this->_toDownload = array(); + return $ret; + } + + function _downloadCallback($msg, $params = null) + { + switch ($msg) { + case 'saveas': + $this->log(1, "downloading $params ..."); + break; + case 'done': + $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes'); + break; + case 'bytesread': + static $bytes; + if (empty($bytes)) { + $bytes = 0; + } + if (!($bytes % 10240)) { + $this->log(1, '.', false); + } + $bytes += $params; + break; + case 'start': + if($params[1] == -1) { + $length = "Unknown size"; + } else { + $length = number_format($params[1], 0, '', ',')." bytes"; + } + $this->log(1, "Starting to download {$params[0]} ($length)"); + break; + } + if (method_exists($this->ui, '_downloadCallback')) + $this->ui->_downloadCallback($msg, $params); + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string + * @param integer + */ + function pushError($errmsg, $code = -1) + { + array_push($this->_errorStack, array($errmsg, $code)); + } + + function getErrorMsgs() + { + $msgs = array(); + $errs = $this->_errorStack; + foreach ($errs as $err) { + $msgs[] = $err[0]; + } + $this->_errorStack = array(); + return $msgs; + } + + /** + * for BC + * + * @deprecated + */ + function sortPkgDeps(&$packages, $uninstall = false) + { + $uninstall ? + $this->sortPackagesForUninstall($packages) : + $this->sortPackagesForInstall($packages); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * This uses the topological sort method from graph theory, and the + * Structures_Graph package to properly sort dependencies for installation. + * @param array an array of downloaded PEAR_Downloader_Packages + * @return array array of array(packagefilename, package.xml contents) + */ + function sortPackagesForInstall(&$packages) + { + require_once 'Structures/Graph.php'; + require_once 'Structures/Graph/Node.php'; + require_once 'Structures/Graph/Manipulator/TopologicalSorter.php'; + $depgraph = new Structures_Graph(true); + $nodes = array(); + $reg = &$this->config->getRegistry(); + foreach ($packages as $i => $package) { + $pname = $reg->parsedPackageNameToString( + array( + 'channel' => $package->getChannel(), + 'package' => strtolower($package->getPackage()), + )); + $nodes[$pname] = new Structures_Graph_Node; + $nodes[$pname]->setData($packages[$i]); + $depgraph->addNode($nodes[$pname]); + } + + $deplinks = array(); + foreach ($nodes as $package => $node) { + $pf = &$node->getData(); + $pdeps = $pf->getDeps(true); + if (!$pdeps) { + continue; + } + + if ($pf->getPackagexmlVersion() == '1.0') { + foreach ($pdeps as $dep) { + if ($dep['type'] != 'pkg' || + (isset($dep['optional']) && $dep['optional'] == 'yes')) { + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pear.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pecl.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + } + } else { + // the only ordering we care about is: + // 1) subpackages must be installed before packages that depend on them + // 2) required deps must be installed before packages that depend on them + if (isset($pdeps['required']['subpackage'])) { + $t = $pdeps['required']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['subpackage'])) { + $t = $group['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + + if (isset($pdeps['optional']['subpackage'])) { + $t = $pdeps['optional']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['required']['package'])) { + $t = $pdeps['required']['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['package'])) { + $t = $group['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + } + } + + $this->_detectDepCycle($deplinks); + foreach ($deplinks as $dependent => $parents) { + foreach ($parents as $parent => $unused) { + $nodes[$dependent]->connectTo($nodes[$parent]); + } + } + + $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph); + $ret = array(); + for ($i = 0, $count = count($installOrder); $i < $count; $i++) { + foreach ($installOrder[$i] as $index => $sortedpackage) { + $data = &$installOrder[$i][$index]->getData(); + $ret[] = &$nodes[$reg->parsedPackageNameToString( + array( + 'channel' => $data->getChannel(), + 'package' => strtolower($data->getPackage()), + ))]->getData(); + } + } + + $packages = $ret; + return; + } + + /** + * Detect recursive links between dependencies and break the cycles + * + * @param array + * @access private + */ + function _detectDepCycle(&$deplinks) + { + do { + $keepgoing = false; + foreach ($deplinks as $dep => $parents) { + foreach ($parents as $parent => $unused) { + // reset the parent cycle detector + $this->_testCycle(null, null, null); + if ($this->_testCycle($dep, $deplinks, $parent)) { + $keepgoing = true; + unset($deplinks[$dep][$parent]); + if (count($deplinks[$dep]) == 0) { + unset($deplinks[$dep]); + } + + continue 3; + } + } + } + } while ($keepgoing); + } + + function _testCycle($test, $deplinks, $dep) + { + static $visited = array(); + if ($test === null) { + $visited = array(); + return; + } + + // this happens when a parent has a dep cycle on another dependency + // but the child is not part of the cycle + if (isset($visited[$dep])) { + return false; + } + + $visited[$dep] = 1; + if ($test == $dep) { + return true; + } + + if (isset($deplinks[$dep])) { + if (in_array($test, array_keys($deplinks[$dep]), true)) { + return true; + } + + foreach ($deplinks[$dep] as $parent => $unused) { + if ($this->_testCycle($test, $deplinks, $parent)) { + return true; + } + } + } + + return false; + } + + /** + * Set up the dependency for installation parsing + * + * @param array $t dependency information + * @param PEAR_Registry $reg + * @param array $deplinks list of dependency links already established + * @param array $nodes all existing package nodes + * @param string $package parent package name + * @access private + */ + function _setupGraph($t, $reg, &$deplinks, &$nodes, $package) + { + foreach ($t as $dep) { + $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel']; + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => $depchannel, + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + $deplinks[$dname][$package] = 1; + } + } + } + + function _dependsOn($a, $b) + { + return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b); + } + + function _checkDepTree($channel, $package, $b, $checked = array()) + { + $checked[$channel][$package] = true; + if (!isset($this->_depTree[$channel][$package])) { + return false; + } + + if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())] + [strtolower($b->getPackage())])) { + return true; + } + + foreach ($this->_depTree[$channel][$package] as $ch => $packages) { + foreach ($packages as $pa => $true) { + if ($this->_checkDepTree($ch, $pa, $b, $checked)) { + return true; + } + } + } + + return false; + } + + function _sortInstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return 1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return -1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependsOn($a, $b)) { + return 1; + } + if ($this->_dependsOn($b, $a)) { + return -1; + } + return 0; + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP/SSL connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir directory to save file in + * @param mixed $callback function/method to call for status + * updates + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @param false|string $channel Channel to use for retrieving authentication + * @return string|array Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, + $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + if ($callback) { + call_user_func($callback, 'setup', array(&$ui)); + } + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + + if (isset($this)) { + $config = &$this->config; + } else { + $config = &PEAR_Config::singleton(); + } + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($config->get('http_proxy') && + $proxy = parse_url($config->get('http_proxy'))) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + + if ($callback) { + call_user_func($callback, 'message', "Using HTTP proxy $host:$port"); + } + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $url HTTP/1.1\r\n"; + $request .= "Host: $host\r\n"; + } else { + $request = "GET $url HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } else { + $network_host = $host; + if (isset($info['scheme']) && $info['scheme'] == 'https') { + $network_host = 'ssl://' . $host; + } + + $fp = @fsockopen($network_host, $port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($host, $port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $path HTTP/1.1\r\n"; + $request .= "Host: $host\r\n"; + } else { + $request = "GET $path HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } + + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n"; + + if (isset($this)) { // only pass in authentication for non-static calls + $username = $config->get('username', null, $channel); + $password = $config->get('password', null, $channel); + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + fwrite($fp, $request); + $headers = array(); + $reply = 0; + while (trim($line = fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], + $ui, $save_dir, $callback, $lastmodified, $accept); + } + + if (isset($headers['content-disposition']) && + preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) { + $save_as = basename($matches[1]); + } else { + $save_as = basename($url); + } + + if ($callback) { + $tmp = call_user_func($callback, 'saveas', $save_as); + if ($tmp) { + $save_as = $tmp; + } + } + + $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as; + if (is_link($dest_file)) { + return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $dest_file . ' as it is symlinked to ' . readlink($dest_file) . ' - Possible symlink attack'); + } + + if (!$wp = @fopen($dest_file, 'wb')) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("could not open $dest_file for writing"); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $bytes = 0; + if ($callback) { + call_user_func($callback, 'start', array(basename($dest_file), $length)); + } + + while ($data = fread($fp, 1024)) { + $bytes += strlen($data); + if ($callback) { + call_user_func($callback, 'bytesread', $bytes); + } + if (!@fwrite($wp, $data)) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("$dest_file: write failed ($php_errormsg)"); + } + } + + fclose($fp); + fclose($wp); + if ($callback) { + call_user_func($callback, 'done', $bytes); + } + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + return array($dest_file, $lastmodified, $headers); + } + return $dest_file; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Downloader/Package.php b/includes/pear/PEAR/Downloader/Package.php new file mode 100644 index 0000000..03a53ee --- /dev/null +++ b/includes/pear/PEAR/Downloader/Package.php @@ -0,0 +1,1988 @@ +<?php +/** + * PEAR_Downloader_Package + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Error code when parameter initialization fails because no releases + * exist within preferred_state, but releases do exist + */ +define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003); +/** + * Error code when parameter initialization fails because no releases + * exist that will work with the existing PHP version + */ +define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004); + +/** + * Coordinates download parameters and manages their dependencies + * prior to downloading them. + * + * Input can come from three sources: + * + * - local files (archives or package.xml) + * - remote files (downloadable urls) + * - abstract package names + * + * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires + * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the + * format returned of dependencies is slightly different from that used in package.xml. + * + * This class hides the differences between these elements, and makes automatic + * dependency resolution a piece of cake. It also manages conflicts when + * two classes depend on incompatible dependencies, or differing versions of the same + * package dependency. In addition, download will not be attempted if the php version is + * not supported, PEAR installer version is not supported, or non-PECL extensions are not + * installed. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Downloader_Package +{ + /** + * @var PEAR_Downloader + */ + var $_downloader; + /** + * @var PEAR_Config + */ + var $_config; + /** + * @var PEAR_Registry + */ + var $_registry; + /** + * Used to implement packagingroot properly + * @var PEAR_Registry + */ + var $_installRegistry; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2 + */ + var $_packagefile; + /** + * @var array + */ + var $_parsedname; + /** + * @var array + */ + var $_downloadURL; + /** + * @var array + */ + var $_downloadDeps = array(); + /** + * @var boolean + */ + var $_valid = false; + /** + * @var boolean + */ + var $_analyzed = false; + /** + * if this or a parent package was invoked with Package-state, this is set to the + * state variable. + * + * This allows temporary reassignment of preferred_state for a parent package and all of + * its dependencies. + * @var string|false + */ + var $_explicitState = false; + /** + * If this package is invoked with Package#group, this variable will be true + */ + var $_explicitGroup = false; + /** + * Package type local|url + * @var string + */ + var $_type; + /** + * Contents of package.xml, if downloaded from a remote channel + * @var string|false + * @access private + */ + var $_rawpackagefile; + /** + * @var boolean + * @access private + */ + var $_validated = false; + + /** + * @param PEAR_Downloader + */ + function PEAR_Downloader_Package(&$downloader) + { + $this->_downloader = &$downloader; + $this->_config = &$this->_downloader->config; + $this->_registry = &$this->_config->getRegistry(); + $options = $downloader->getOptions(); + if (isset($options['packagingroot'])) { + $this->_config->setInstallRoot($options['packagingroot']); + $this->_installRegistry = &$this->_config->getRegistry(); + $this->_config->setInstallRoot(false); + } else { + $this->_installRegistry = &$this->_registry; + } + $this->_valid = $this->_analyzed = false; + } + + /** + * Parse the input and determine whether this is a local file, a remote uri, or an + * abstract package name. + * + * This is the heart of the PEAR_Downloader_Package(), and is used in + * {@link PEAR_Downloader::download()} + * @param string + * @return bool|PEAR_Error + */ + function initialize($param) + { + $origErr = $this->_fromFile($param); + if ($this->_valid) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($options['offline'])) { + if (PEAR::isError($origErr) && !isset($options['soft'])) { + foreach ($origErr->getUserInfo() as $userInfo) { + if (isset($userInfo['message'])) { + $this->_downloader->log(0, $userInfo['message']); + } + } + + $this->_downloader->log(0, $origErr->getMessage()); + } + + return PEAR::raiseError('Cannot download non-local package "' . $param . '"'); + } + + $err = $this->_fromUrl($param); + if (PEAR::isError($err) || !$this->_valid) { + if ($this->_type == 'url') { + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError("Invalid or missing remote package file"); + } + + $err = $this->_fromString($param); + if (PEAR::isError($err) || !$this->_valid) { + if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) { + return false; // instruct the downloader to silently skip + } + + if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) { + if (is_array($origErr->getUserInfo())) { + foreach ($origErr->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $origErr->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + } + + return true; + } + + /** + * Retrieve any non-local packages + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error + */ + function &download() + { + if (isset($this->_packagefile)) { + return $this->_packagefile; + } + + if (isset($this->_downloadURL['url'])) { + $this->_isvalid = false; + $info = $this->getParsedPackage(); + foreach ($info as $i => $p) { + $info[$i] = strtolower($p); + } + + $err = $this->_fromUrl($this->_downloadURL['url'], + $this->_registry->parsedPackageNameToString($this->_parsedname, true)); + $newinfo = $this->getParsedPackage(); + foreach ($newinfo as $i => $p) { + $newinfo[$i] = strtolower($p); + } + + if ($info != $newinfo) { + do { + if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') { + $info['channel'] = 'pear.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') { + $info['channel'] = 'pecl.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + + return PEAR::raiseError('CRITICAL ERROR: We are ' . + $this->_registry->parsedPackageNameToString($info) . ', but the file ' . + 'downloaded claims to be ' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage())); + } while (false); + } + + if (PEAR::isError($err) || !$this->_valid) { + return $err; + } + } + + $this->_type = 'local'; + return $this->_packagefile; + } + + function &getPackageFile() + { + return $this->_packagefile; + } + + function &getDownloader() + { + return $this->_downloader; + } + + function getType() + { + return $this->_type; + } + + /** + * Like {@link initialize()}, but operates on a dependency + */ + function fromDepURL($dep) + { + $this->_downloadURL = $dep; + if (isset($dep['uri'])) { + $options = $this->_downloader->getOptions(); + if (!extension_loaded("zlib") || isset($options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->_fromUrl($dep['uri'] . $ext); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' . + 'cannot download'); + } + } else { + $this->_parsedname = + array( + 'package' => $dep['info']->getPackage(), + 'channel' => $dep['info']->getChannel(), + 'version' => $dep['version'] + ); + if (!isset($dep['nodefault'])) { + $this->_parsedname['group'] = 'default'; // download the default dependency group + $this->_explicitGroup = false; + } + + $this->_rawpackagefile = $dep['raw']; + } + } + + function detectDependencies($params) + { + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + return; + } + + if (isset($options['offline'])) { + $this->_downloader->log(3, 'Skipping dependency download check, --offline specified'); + return; + } + + $pname = $this->getParsedPackage(); + if (!$pname) { + return; + } + + $deps = $this->getDeps(); + if (!$deps) { + return; + } + + if (isset($deps['required'])) { // package.xml 2.0 + return $this->_detect2($deps, $pname, $options, $params); + } + + return $this->_detect1($deps, $pname, $options, $params); + } + + function setValidated() + { + $this->_validated = true; + } + + function alreadyValidated() + { + return $this->_validated; + } + + /** + * Remove packages to be downloaded that are already installed + * @param array of PEAR_Downloader_Package objects + * @static + */ + function removeInstalled(&$params) + { + if (!isset($params[0])) { + return; + } + + $options = $params[0]->_downloader->getOptions(); + if (!isset($options['downloadonly'])) { + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + // remove self if already installed with this version + // this does not need any pecl magic - we only remove exact matches + if ($param->_installRegistry->packageExists($package, $channel)) { + $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $param->getVersion(), '==')) { + if (!isset($options['force'])) { + $info = $param->getParsedPackage(); + unset($info['version']); + unset($info['state']); + if (!isset($options['soft'])) { + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + } + $params[$i] = false; + } + } elseif (!isset($options['force']) && !isset($options['upgrade']) && + !isset($options['soft'])) { + $info = $param->getParsedPackage(); + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + $params[$i] = false; + } + } + } + } + + PEAR_Downloader_Package::removeDuplicates($params); + } + + function _detect2($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $groupnotfound = false; + foreach (array('package', 'subpackage') as $packagetype) { + // get required dependency group + if (isset($deps['required'][$packagetype])) { + if (isset($deps['required'][$packagetype][0])) { + foreach ($deps['required'][$packagetype] as $dep) { + if (isset($dep['conflicts'])) { + // skip any package that this package conflicts with + continue; + } + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $dep = $deps['required'][$packagetype]; + if (!isset($dep['conflicts'])) { + // skip any package that this package conflicts with + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + + // get optional dependency group, if any + if (isset($deps['optional'][$packagetype])) { + $skipnames = array(); + if (!isset($deps['optional'][$packagetype][0])) { + $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]); + } + + foreach ($deps['optional'][$packagetype] as $dep) { + $skip = false; + if (!isset($options['alldeps'])) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage(), + true) . '" optional dependency "' . + $this->_registry->parsedPackageNameToString(array('package' => + $dep['name'], 'channel' => 'pear.php.net'), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true); + $skip = true; + unset($dep['package']); + } + + $ret = $this->_detect2Dep($dep, $pname, 'optional', $params); + if (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + if (!$ret) { + $dep['package'] = $dep['name']; + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + } + + if (!$skip && is_array($ret)) { + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download optional dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps to download automatically'); + } + } + } + + // get requested dependency group, if any + $groupname = $this->getGroup(); + $explicit = $this->_explicitGroup; + if (!$groupname) { + if (!$this->canDefault()) { + continue; + } + + $groupname = 'default'; // try the default dependency group + } + + if ($groupnotfound) { + continue; + } + + if (isset($deps['group'])) { + if (isset($deps['group']['attribs'])) { + if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) { + $group = $deps['group']; + } elseif ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + + $groupnotfound = true; + continue; + } + } else { + $found = false; + foreach ($deps['group'] as $group) { + if (strtolower($group['attribs']['name']) == strtolower($groupname)) { + $found = true; + break; + } + } + + if (!$found) { + if ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + } + + $groupnotfound = true; + continue; + } + } + } + + if (isset($group) && isset($group[$packagetype])) { + if (isset($group[$packagetype][0])) { + foreach ($group[$packagetype] as $dep) { + $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $ret = $this->_detect2Dep($group[$packagetype], $pname, + 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + } + + function _detect2Dep($dep, $pname, $group, $params) + { + if (isset($dep['conflicts'])) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($dep['uri'])) { + return array('uri' => $dep['uri'], 'dep' => $dep);; + } + + $testdep = $dep; + $testdep['package'] = $dep['name']; + if (PEAR_Downloader_Package::willDownload($testdep, $params)) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + } + return false; + } + + $options = $this->_downloader->getOptions(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + return $url; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + return false; + } + + // check to see if a dep is already installed and is the same or newer + if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) { + $oper = 'has'; + } else { + $oper = 'gt'; + } + + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled($ret, $oper)) { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']); + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '" version ' . $url['version'] . ', already installed as version ' . + $version); + } + + return false; + } + + if (isset($dep['nodefault'])) { + $ret['nodefault'] = true; + } + + return $ret; + } + + function _detect1($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $skipnames = array(); + foreach ($deps as $dep) { + $nodownload = false; + if (isset ($dep['type']) && $dep['type'] === 'pkg') { + $dep['channel'] = 'pear.php.net'; + $dep['package'] = $dep['name']; + switch ($dep['rel']) { + case 'not' : + continue 2; + case 'ge' : + case 'eq' : + case 'gt' : + case 'has' : + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + if (PEAR_Downloader_Package::willDownload($dep, $params)) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + continue 2; + } + $fakedp = new PEAR_PackageFile_v1; + $fakedp->setPackage($dep['name']); + // skip internet check if we are not upgrading (bug #5810) + if (!isset($options['upgrade']) && $this->isInstalled( + $fakedp, $dep['rel'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", is already installed'); + continue 2; + } + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + $chan = 'pear.php.net'; + if (PEAR::isError($url)) { + // check to see if this is a pecl package that has jumped + // from pear.php.net to pecl.php.net channel + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($dep); + $newdep = $newdep[0]; + $newdep['channel'] = 'pecl.php.net'; + $chan = 'pecl.php.net'; + $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname); + $obj = &$this->_installRegistry->getPackage($dep['name']); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . + ': Skipping ' . $group . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $obj->getVersion()); + } + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + continue; + } else { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + $this->_downloader->log(2, $this->getShortName() . + ': Skipping optional dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", no releases exist'); + continue; + } else { + return $url; + } + } + } + } + + PEAR::popErrorHandling(); + if (!isset($options['alldeps'])) { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" optional dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" required dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + // check to see if a dep is already installed + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled( + $url, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (isset($newdep)) { + $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']); + } else { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version'); + } + + $dep['version'] = $url['version']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $version); + } + + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + + continue; + } + + if ($nodownload) { + continue; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (isset($newdep)) { + $dep = $newdep; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, + isset($dep['optional']) && $dep['optional'] == 'yes' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + continue; + } + + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps or --onlyreqdeps to download automatically'); + } + } + } + + function setDownloadURL($pkg) + { + $this->_downloadURL = $pkg; + } + + /** + * Set the package.xml object for this downloaded package + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg + */ + function setPackageFile(&$pkg) + { + $this->_packagefile = &$pkg; + } + + function getShortName() + { + return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(), + 'package' => $this->getPackage()), true); + } + + function getParsedPackage() + { + if (isset($this->_packagefile) || isset($this->_parsedname)) { + return array('channel' => $this->getChannel(), + 'package' => $this->getPackage(), + 'version' => $this->getVersion()); + } + + return false; + } + + function getDownloadURL() + { + return $this->_downloadURL; + } + + function canDefault() + { + if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) { + return false; + } + + return true; + } + + function getPackage() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackage(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackage(); + } + + return false; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function isSubpackage(&$pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isSubpackage($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isSubpackage($pf); + } + + return false; + } + + function getPackageType() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackageType(); + } + + return false; + } + + function isBundle() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType() == 'bundle'; + } + + return false; + } + + function getPackageXmlVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackagexmlVersion(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackagexmlVersion(); + } + + return '1.0'; + } + + function getChannel() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getChannel(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getChannel(); + } + + return false; + } + + function getURI() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getURI(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getURI(); + } + + return false; + } + + function getVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getVersion(); + } elseif (isset($this->_downloadURL['version'])) { + return $this->_downloadURL['version']; + } + + return false; + } + + function isCompatible($pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isCompatible($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isCompatible($pf); + } + + return true; + } + + function setGroup($group) + { + $this->_parsedname['group'] = $group; + } + + function getGroup() + { + if (isset($this->_parsedname['group'])) { + return $this->_parsedname['group']; + } + + return ''; + } + + function isExtension($name) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isExtension($name); + } elseif (isset($this->_downloadURL['info'])) { + if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') { + return $this->_downloadURL['info']->getProvidesExtension() == $name; + } + + return false; + } + + return false; + } + + function getDeps() + { + if (isset($this->_packagefile)) { + $ver = $this->_packagefile->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_packagefile->getDeps(true); + } + + return $this->_packagefile->getDeps(); + } elseif (isset($this->_downloadURL['info'])) { + $ver = $this->_downloadURL['info']->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_downloadURL['info']->getDeps(true); + } + + return $this->_downloadURL['info']->getDeps(); + } + + return array(); + } + + /** + * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency + * returned from getDepDownloadURL() + */ + function isEqual($param) + { + if (is_object($param)) { + $channel = $param->getChannel(); + $package = $param->getPackage(); + if ($param->getURI()) { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + 'uri' => $param->getURI(), + ); + } else { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + ); + } + } else { + if (isset($param['uri'])) { + if ($this->getChannel() != '__uri') { + return false; + } + return $param['uri'] == $this->getURI(); + } + + $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage(); + $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel(); + if (isset($param['rel'])) { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($param); + $newdep = $newdep[0]; + } elseif (isset($param['min'])) { + $newdep = $param; + } + } + + if (isset($newdep)) { + if (!isset($newdep['min'])) { + $newdep['min'] = '0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '100000000000000000000'; + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + // we need to support both dependency possibilities + if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pecl.php.net'; + } + } + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pear.php.net'; + } + } + + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + version_compare($newdep['min'], $this->getVersion(), '<=') && + version_compare($newdep['max'], $this->getVersion(), '>=')); + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if (strtolower($package) == strtolower($this->getPackage())) { + $channel = 'pear.php.net'; + } + } + + if (isset($param['version'])) { + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + $param['version'] == $this->getVersion()); + } + + return strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel(); + } + + function isInstalled($dep, $oper = '==') + { + if (!$dep) { + return false; + } + + if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') { + return false; + } + + if (is_object($dep)) { + $package = $dep->getPackage(); + $channel = $dep->getChannel(); + if ($dep->getURI()) { + $dep = array( + 'uri' => $dep->getURI(), + 'version' => $dep->getVersion(), + ); + } else { + $dep = array( + 'version' => $dep->getVersion(), + ); + } + } else { + if (isset($dep['uri'])) { + $channel = '__uri'; + $package = $dep['dep']['name']; + } else { + $channel = $dep['info']->getChannel(); + $package = $dep['info']->getPackage(); + } + } + + $options = $this->_downloader->getOptions(); + $test = $this->_installRegistry->packageExists($package, $channel); + if (!$test && $channel == 'pecl.php.net') { + // do magic to allow upgrading from old pecl packages to new ones + $test = $this->_installRegistry->packageExists($package, 'pear.php.net'); + $channel = 'pear.php.net'; + } + + if ($test) { + if (isset($dep['uri'])) { + if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) { + return true; + } + } + + if (isset($options['upgrade'])) { + $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $dep['version'], '>=')) { + return true; + } + + return false; + } + + return true; + } + + return false; + } + + /** + * Detect duplicate package names with differing versions + * + * If a user requests to install Date 1.4.6 and Date 1.4.7, + * for instance, this is a logic error. This method + * detects this situation. + * + * @param array $params array of PEAR_Downloader_Package objects + * @param array $errorparams empty array + * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts + */ + function detectStupidDuplicates($params, &$errorparams) + { + $existing = array(); + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + $group = $param->getGroup(); + if (!isset($existing[$channel . '/' . $package])) { + $existing[$channel . '/' . $package] = array(); + } + + if (!isset($existing[$channel . '/' . $package][$group])) { + $existing[$channel . '/' . $package][$group] = array(); + } + + $existing[$channel . '/' . $package][$group][] = $i; + } + + $indices = array(); + foreach ($existing as $package => $groups) { + foreach ($groups as $group => $dupes) { + if (count($dupes) > 1) { + $indices = $indices + $dupes; + } + } + } + + $indices = array_unique($indices); + foreach ($indices as $index) { + $errorparams[] = $params[$index]; + } + + return count($errorparams); + } + + /** + * @param array + * @param bool ignore install groups - for final removal of dupe packages + * @static + */ + function removeDuplicates(&$params, $ignoreGroups = false) + { + $pnames = array(); + foreach ($params as $i => $param) { + if (!$param) { + continue; + } + + if ($param->getPackage()) { + $group = $ignoreGroups ? '' : $param->getGroup(); + $pnames[$i] = $param->getChannel() . '/' . + $param->getPackage() . '-' . $param->getVersion() . '#' . $group; + } + } + + $pnames = array_unique($pnames); + $unset = array_diff(array_keys($params), array_keys($pnames)); + $testp = array_flip($pnames); + foreach ($params as $i => $param) { + if (!$param) { + $unset[] = $i; + continue; + } + + if (!is_a($param, 'PEAR_Downloader_Package')) { + $unset[] = $i; + continue; + } + + $group = $ignoreGroups ? '' : $param->getGroup(); + if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' . + $param->getVersion() . '#' . $group])) { + $unset[] = $i; + } + } + + foreach ($unset as $i) { + unset($params[$i]); + } + + $ret = array(); + foreach ($params as $i => $param) { + $ret[] = &$params[$i]; + } + + $params = array(); + foreach ($ret as $i => $param) { + $params[] = &$ret[$i]; + } + } + + function explicitState() + { + return $this->_explicitState; + } + + function setExplicitState($s) + { + $this->_explicitState = $s; + } + + /** + * @static + */ + function mergeDependencies(&$params) + { + $bundles = $newparams = array(); + foreach ($params as $i => $param) { + if (!$param->isBundle()) { + continue; + } + + $bundles[] = $i; + $pf = &$param->getPackageFile(); + $newdeps = array(); + $contents = $pf->getBundledPackages(); + if (!is_array($contents)) { + $contents = array($contents); + } + + foreach ($contents as $file) { + $filecontents = $pf->getFileContents($file); + $dl = &$param->getDownloader(); + $options = $dl->getOptions(); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + return $dir; + } + + $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb'); + if (!$fp) { + continue; + } + + // FIXME do symlink check + + fwrite($fp, $filecontents, strlen($filecontents)); + fclose($fp); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + PEAR::popErrorHandling(); + return $dir; + } + + $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $dl->log(0, $e->getMessage()); + } + continue; + } + + $j = &$obj; + if (!PEAR_Downloader_Package::willDownload($j, + array_merge($params, $newparams)) && !$param->isInstalled($j)) { + $newparams[] = &$j; + } + } + } + + foreach ($bundles as $i) { + unset($params[$i]); // remove bundles - only their contents matter for installation + } + + PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices + if (count($newparams)) { // add in bundled packages for install + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + $newparams = array(); + } + + foreach ($params as $i => $param) { + $newdeps = array(); + foreach ($param->_downloadDeps as $dep) { + $merge = array_merge($params, $newparams); + if (!PEAR_Downloader_Package::willDownload($dep, $merge) + && !$param->isInstalled($dep) + ) { + $newdeps[] = $dep; + } else { + //var_dump($dep); + // detect versioning conflicts here + } + } + + // convert the dependencies into PEAR_Downloader_Package objects for the next time around + $params[$i]->_downloadDeps = array(); + foreach ($newdeps as $dep) { + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $e = $obj->fromDepURL($dep); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + continue; + } + + $e = $obj->detectDependencies($params); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + } + + $j = &$obj; + $newparams[] = &$j; + } + } + + if (count($newparams)) { + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + return true; + } + + return false; + } + + + /** + * @static + */ + function willDownload($param, $params) + { + if (!is_array($params)) { + return false; + } + + foreach ($params as $obj) { + if ($obj->isEqual($param)) { + return true; + } + } + + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d) + { + $a = &new PEAR_PackageFile($c, $d); + return $a; + } + + /** + * This will retrieve from a local file if possible, and parse out + * a group name as well. The original parameter will be modified to reflect this. + * @param string|array can be a parsed package name as well + * @access private + */ + function _fromFile(&$param) + { + $saveparam = $param; + if (is_string($param)) { + if (!@file_exists($param)) { + $test = explode('#', $param); + $group = array_pop($test); + if (@file_exists(implode('#', $test))) { + $this->setGroup($group); + $param = implode('#', $test); + $this->_explicitGroup = true; + } + } + + if (@is_file($param)) { + $this->_type = 'local'; + $options = $this->_downloader->getOptions(); + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->_debug); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + $this->_valid = false; + $param = $saveparam; + return $pf; + } + $this->_packagefile = &$pf; + if (!$this->getGroup()) { + $this->setGroup('default'); // install the default dependency group + } + return $this->_valid = true; + } + } + $param = $saveparam; + return $this->_valid = false; + } + + function _fromUrl($param, $saveparam = '') + { + if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) { + $options = $this->_downloader->getOptions(); + $this->_type = 'url'; + $callback = $this->_downloader->ui ? + array(&$this->_downloader, '_downloadCallback') : null; + $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + $this->_downloader->popErrorHandling(); + return $dir; + } + + $this->_downloader->log(3, 'Downloading "' . $param . '"'); + $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui, + $dir, $callback, null, false, $this->getChannel()); + $this->_downloader->popErrorHandling(); + if (PEAR::isError($file)) { + if (!empty($saveparam)) { + $saveparam = ", cannot download \"$saveparam\""; + } + $err = PEAR::raiseError('Could not download from "' . $param . + '"' . $saveparam . ' (' . $file->getMessage() . ')'); + return $err; + } + + if ($this->_rawpackagefile) { + require_once 'Archive/Tar.php'; + $tar = &new Archive_Tar($file); + $packagexml = $tar->extractInString('package2.xml'); + if (!$packagexml) { + $packagexml = $tar->extractInString('package.xml'); + } + + if (str_replace(array("\n", "\r"), array('',''), $packagexml) != + str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) { + if ($this->getChannel() != 'pear.php.net') { + return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' . + 'not match value returned from xml-rpc'); + } + + // be more lax for the existing PEAR packages that have not-ok + // characters in their package.xml + $this->_downloader->log(0, 'CRITICAL WARNING: The "' . + $this->getPackage() . '" package has invalid characters in its ' . + 'package.xml. The next version of PEAR may not be able to install ' . + 'this package for security reasons. Please open a bug report at ' . + 'http://pear.php.net/package/' . $this->getPackage() . '/bugs'); + } + } + + // whew, download worked! + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug); + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, "Validation Error: $err"); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pf->getMessage()); + } + + ///FIXME need to pass back some error code that we can use to match with to cancel all further operations + /// At least stop all deps of this package from being installed + $out = $saveparam ? $saveparam : $param; + $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive'); + $this->_valid = false; + return $err; + } + + $this->_packagefile = &$pf; + $this->setGroup('default'); // install the default dependency group + return $this->_valid = true; + } + + return $this->_valid = false; + } + + /** + * + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',]) + * or a string of format [channame/]pname[-version|-state] + */ + function _fromString($param) + { + $options = $this->_downloader->getOptions(); + $channel = $this->_config->get('default_channel'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($pname)) { + if ($pname->getCode() == 'invalid') { + $this->_valid = false; + return false; + } + + if ($pname->getCode() == 'channel') { + $parsed = $pname->getUserInfo(); + if ($this->_downloader->discover($parsed['channel'])) { + if ($this->_config->get('auto_discover')) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Channel "' . $parsed['channel'] . + '" is not initialized, use ' . + '"pear channel-discover ' . $parsed['channel'] . '" to initialize ' . + 'or pear config-set auto_discover 1'); + } + } + } + + if (PEAR::isError($pname)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } + + if (!isset($this->_type)) { + $this->_type = 'rest'; + } + + $this->_parsedname = $pname; + $this->_explicitState = isset($pname['state']) ? $pname['state'] : false; + $this->_explicitGroup = isset($pname['group']) ? true : false; + + $info = $this->_downloader->_getPackageDownloadUrl($pname); + if (PEAR::isError($info)) { + if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') { + // try pecl + $pname['channel'] = 'pecl.php.net'; + if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) { + if (!PEAR::isError($test)) { + $info = PEAR::raiseError($info->getMessage() . ' - package ' . + $this->_registry->parsedPackageNameToString($pname, true) . + ' can be installed with "pecl install ' . $pname['package'] . + '"'); + } else { + $pname['channel'] = 'pear.php.net'; + } + } else { + $pname['channel'] = 'pear.php.net'; + } + } + + return $info; + } + + $this->_rawpackagefile = $info['raw']; + $ret = $this->_analyzeDownloadURL($info, $param, $pname); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($ret) { + $this->_downloadURL = $ret; + return $this->_valid = (bool) $ret; + } + } + + /** + * @param array output of package.getDownloadURL + * @param string|array|object information for detecting packages to be downloaded, and + * for errors + * @param array name information of the package + * @param array|null packages to be downloaded + * @param bool is this an optional dependency? + * @param bool is this any kind of dependency? + * @access private + */ + function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false, + $isdependency = false) + { + if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) { + return false; + } + + if ($info === false) { + $saveparam = !is_string($param) ? ", cannot download \"$param\"" : ''; + + // no releases exist + return PEAR::raiseError('No releases for package "' . + $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam); + } + + if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) { + $err = false; + if ($pname['channel'] == 'pecl.php.net') { + if ($info['info']->getChannel() != 'pear.php.net') { + $err = true; + } + } elseif ($info['info']->getChannel() == 'pecl.php.net') { + if ($pname['channel'] != 'pear.php.net') { + $err = true; + } + } else { + $err = true; + } + + if ($err) { + return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] . + '" retrieved another channel\'s name for download! ("' . + $info['info']->getChannel() . '")'); + } + } + + $preferred_state = $this->_config->get('preferred_state'); + if (!isset($info['url'])) { + $package_version = $this->_registry->packageInfo($info['info']->getPackage(), + 'version', $info['info']->getChannel()); + if ($this->isInstalled($info)) { + if ($isdependency && version_compare($info['version'], $package_version, '<=')) { + // ignore bogus errors of "failed to download dependency" + // if it is already installed and the one that would be + // downloaded is older or the same version (Bug #7219) + return false; + } + } + + if ($info['version'] === $package_version) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' . + ' (' . $package_version . ') is the same as the locally installed one.'); + } + + return false; + } + + if (version_compare($info['version'], $package_version, '<=')) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' . + ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').'); + } + + return false; + } + + $instead = ', will instead download version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '"'; + // releases exist, but we failed to get any + if (isset($this->_downloader->_options['force'])) { + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . + '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + $instead = ''; + } + } else { + $vs = ' within preferred state "' . $preferred_state . '"'; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . $vs . $instead); + } + + // download the latest release + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } else { + if (isset($info['php']) && $info['php']) { + $err = PEAR::raiseError('Failed to download ' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], + 'package' => $pname['package']), + true) . + ', latest release is version ' . $info['php']['v'] . + ', but it requires PHP version "' . + $info['php']['m'] . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['php']['v'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_PHPVERSION); + return $err; + } + + // construct helpful error message + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + } + } else { + $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"'; + } + + $options = $this->_downloader->getOptions(); + // this is only set by the "download-all" command + if (isset($options['ignorepreferred_state'])) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_STATE); + return $err; + } + + // Checks if the user has a package installed already and checks the release against + // the state against the installed package, this allows upgrades for packages + // with lower stability than the preferred_state + $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']); + if (!$this->isInstalled($info) + || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true)) + ) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install'); + return $err; + } + } + } + + if (isset($info['deprecated']) && $info['deprecated']) { + $this->_downloader->log(0, + 'WARNING: "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $info['info']->getChannel(), + 'package' => $info['info']->getPackage()), true) . + '" is deprecated in favor of "' . + $this->_registry->parsedPackageNameToString($info['deprecated'], true) . + '"'); + } + + return $info; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/ErrorStack.php b/includes/pear/PEAR/ErrorStack.php new file mode 100644 index 0000000..2cdddfc --- /dev/null +++ b/includes/pear/PEAR/ErrorStack.php @@ -0,0 +1,985 @@ +<?php +/** + * Error Stack Implementation + * + * This is an incredibly simple implementation of a very complex error handling + * facility. It contains the ability + * to track multiple errors from multiple packages simultaneously. In addition, + * it can track errors of many levels, save data along with the error, context + * information such as the exact file, line number, class and function that + * generated the error, and if necessary, it can raise a traditional PEAR_Error. + * It has built-in support for PEAR::Log, to log errors as they occur + * + * Since version 0.2alpha, it is also possible to selectively ignore errors, + * through the use of an error callback, see {@link pushCallback()} + * + * Since version 0.3alpha, it is possible to specify the exception class + * returned from {@link push()} + * + * Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can + * still be done quite handily in an error callback or by manipulating the returned array + * @category Debugging + * @package PEAR_ErrorStack + * @author Greg Beaver <cellog@php.net> + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ + +/** + * Singleton storage + * + * Format: + * <pre> + * array( + * 'package1' => PEAR_ErrorStack object, + * 'package2' => PEAR_ErrorStack object, + * ... + * ) + * </pre> + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] + */ +$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array(); + +/** + * Global error callback (default) + * + * This is only used if set to non-false. * is the default callback for + * all packages, whereas specific packages may set a default callback + * for all instances, regardless of whether they are a singleton or not. + * + * To exclude non-singletons, only set the local callback for the singleton + * @see PEAR_ErrorStack::setDefaultCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array( + '*' => false, +); + +/** + * Global Log object (default) + * + * This is only used if set to non-false. Use to set a default log object for + * all stacks, regardless of instantiation order or location + * @see PEAR_ErrorStack::setDefaultLogger() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false; + +/** + * Global Overriding Callback + * + * This callback will override any error callbacks that specific loggers have set. + * Use with EXTREME caution + * @see PEAR_ErrorStack::staticPushCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + +/**#@+ + * One of four possible return values from the error Callback + * @see PEAR_ErrorStack::_errorCallback() + */ +/** + * If this is returned, then the error will be both pushed onto the stack + * and logged. + */ +define('PEAR_ERRORSTACK_PUSHANDLOG', 1); +/** + * If this is returned, then the error will only be pushed onto the stack, + * and not logged. + */ +define('PEAR_ERRORSTACK_PUSH', 2); +/** + * If this is returned, then the error will only be logged, but not pushed + * onto the error stack. + */ +define('PEAR_ERRORSTACK_LOG', 3); +/** + * If this is returned, then the error is completely ignored. + */ +define('PEAR_ERRORSTACK_IGNORE', 4); +/** + * If this is returned, then the error is logged and die() is called. + */ +define('PEAR_ERRORSTACK_DIE', 5); +/**#@-*/ + +/** + * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in + * the singleton method. + */ +define('PEAR_ERRORSTACK_ERR_NONCLASS', 1); + +/** + * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()} + * that has no __toString() method + */ +define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2); +/** + * Error Stack Implementation + * + * Usage: + * <code> + * // global error stack + * $global_stack = &PEAR_ErrorStack::singleton('MyPackage'); + * // local error stack + * $local_stack = new PEAR_ErrorStack('MyPackage'); + * </code> + * @author Greg Beaver <cellog@php.net> + * @version @package_version@ + * @package PEAR_ErrorStack + * @category Debugging + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ +class PEAR_ErrorStack { + /** + * Errors are stored in the order that they are pushed on the stack. + * @since 0.4alpha Errors are no longer organized by error level. + * This renders pop() nearly unusable, and levels could be more easily + * handled in a callback anyway + * @var array + * @access private + */ + var $_errors = array(); + + /** + * Storage of errors by level. + * + * Allows easy retrieval and deletion of only errors from a particular level + * @since PEAR 1.4.0dev + * @var array + * @access private + */ + var $_errorsByLevel = array(); + + /** + * Package name this error stack represents + * @var string + * @access protected + */ + var $_package; + + /** + * Determines whether a PEAR_Error is thrown upon every error addition + * @var boolean + * @access private + */ + var $_compat = false; + + /** + * If set to a valid callback, this will be used to generate the error + * message from the error code, otherwise the message passed in will be + * used + * @var false|string|array + * @access private + */ + var $_msgCallback = false; + + /** + * If set to a valid callback, this will be used to generate the error + * context for an error. For PHP-related errors, this will be a file + * and line number as retrieved from debug_backtrace(), but can be + * customized for other purposes. The error might actually be in a separate + * configuration file, or in a database query. + * @var false|string|array + * @access protected + */ + var $_contextCallback = false; + + /** + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one an PEAR_ERRORSTACK_* constant + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @var false|string|array + * @access protected + */ + var $_errorCallback = array(); + + /** + * PEAR::Log object for logging errors + * @var false|Log + * @access protected + */ + var $_logger = false; + + /** + * Error messages - designed to be overridden + * @var array + * @abstract + */ + var $_errorMsgs = array(); + + /** + * Set up a new error stack + * + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + */ + function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false) + { + $this->_package = $package; + $this->setMessageCallback($msgCallback); + $this->setContextCallback($contextCallback); + $this->_compat = $throwPEAR_Error; + } + + /** + * Return a single error stack for this package. + * + * Note that all parameters are ignored if the stack for package $package + * has already been instantiated + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + * @param string $stackClass class to instantiate + * @static + * @return PEAR_ErrorStack + */ + function &singleton($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack') + { + if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + if (!class_exists($stackClass)) { + if (function_exists('debug_backtrace')) { + $trace = debug_backtrace(); + } + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS, + 'exception', array('stackclass' => $stackClass), + 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)', + false, $trace); + } + $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] = + new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error); + + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + + /** + * Internal error handler for PEAR_ErrorStack class + * + * Dies if the error is an exception (and would have died anyway) + * @access private + */ + function _handleError($err) + { + if ($err['level'] == 'exception') { + $message = $err['message']; + if (isset($_SERVER['REQUEST_URI'])) { + echo '<br />'; + } else { + echo "\n"; + } + var_dump($err['context']); + die($message); + } + } + + /** + * Set up a PEAR::Log object for all error stacks that don't have one + * @param Log $log + * @static + */ + function setDefaultLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } elseif (is_callable($log)) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } + } + + /** + * Set up a PEAR::Log object for this error stack + * @param Log $log + */ + function setLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $this->_logger = &$log; + } elseif (is_callable($log)) { + $this->_logger = &$log; + } + } + + /** + * Set an error code => error message mapping callback + * + * This method sets the callback that can be used to generate error + * messages for any instance + * @param array|string Callback function/method + */ + function setMessageCallback($msgCallback) + { + if (!$msgCallback) { + $this->_msgCallback = array(&$this, 'getErrorMessage'); + } else { + if (is_callable($msgCallback)) { + $this->_msgCallback = $msgCallback; + } + } + } + + /** + * Get an error code => error message mapping callback + * + * This method returns the current callback that can be used to generate error + * messages + * @return array|string|false Callback function/method or false if none + */ + function getMessageCallback() + { + return $this->_msgCallback; + } + + /** + * Sets a default callback to be used by all error stacks + * + * This method sets the callback that can be used to generate error + * messages for a singleton + * @param array|string Callback function/method + * @param string Package name, or false for all packages + * @static + */ + function setDefaultCallback($callback = false, $package = false) + { + if (!is_callable($callback)) { + $callback = false; + } + $package = $package ? $package : '*'; + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback; + } + + /** + * Set a callback that generates context information (location of error) for an error stack + * + * This method sets the callback that can be used to generate context + * information for an error. Passing in NULL will disable context generation + * and remove the expensive call to debug_backtrace() + * @param array|string|null Callback function/method + */ + function setContextCallback($contextCallback) + { + if ($contextCallback === null) { + return $this->_contextCallback = false; + } + if (!$contextCallback) { + $this->_contextCallback = array(&$this, 'getFileLine'); + } else { + if (is_callable($contextCallback)) { + $this->_contextCallback = $contextCallback; + } + } + } + + /** + * Set an error Callback + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one of the ERRORSTACK_* constants. + * + * This functionality can be used to emulate PEAR's pushErrorHandling, and + * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of + * the error stack or logging + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see popCallback() + * @param string|array $cb + */ + function pushCallback($cb) + { + array_push($this->_errorCallback, $cb); + } + + /** + * Remove a callback from the error callback stack + * @see pushCallback() + * @return array|string|false + */ + function popCallback() + { + if (!count($this->_errorCallback)) { + return false; + } + return array_pop($this->_errorCallback); + } + + /** + * Set a temporary overriding error callback for every package error stack + * + * Use this to temporarily disable all existing callbacks (can be used + * to emulate the @ operator, for instance) + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see staticPopCallback(), pushCallback() + * @param string|array $cb + * @static + */ + function staticPushCallback($cb) + { + array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb); + } + + /** + * Remove a temporary overriding error callback + * @see staticPushCallback() + * @return array|string|false + * @static + */ + function staticPopCallback() + { + $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']); + if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) { + $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + } + return $ret; + } + + /** + * Add an error to the stack + * + * If the message generator exists, it is called with 2 parameters. + * - the current Error Stack object + * - an array that is in the same format as an error. Available indices + * are 'code', 'package', 'time', 'params', 'level', and 'context' + * + * Next, if the error should contain context information, this is + * handled by the context grabbing method. + * Finally, the error is pushed onto the proper error stack + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. If a PEAR_Error is returned, the userinfo + * property is set to the following array: + * + * <code> + * array( + * 'code' => $code, + * 'params' => $params, + * 'package' => $this->_package, + * 'level' => $level, + * 'time' => time(), + * 'context' => $context, + * 'message' => $msg, + * //['repackage' => $err] repackaged error array/Exception class + * ); + * </code> + * + * Normally, the previous array is returned. + */ + function push($code, $level = 'error', $params = array(), $msg = false, + $repackage = false, $backtrace = false) + { + $context = false; + // grab error context + if ($this->_contextCallback) { + if (!$backtrace) { + $backtrace = debug_backtrace(); + } + $context = call_user_func($this->_contextCallback, $code, $params, $backtrace); + } + + // save error + $time = explode(' ', microtime()); + $time = $time[1] + $time[0]; + $err = array( + 'code' => $code, + 'params' => $params, + 'package' => $this->_package, + 'level' => $level, + 'time' => $time, + 'context' => $context, + 'message' => $msg, + ); + + if ($repackage) { + $err['repackage'] = $repackage; + } + + // set up the error message, if necessary + if ($this->_msgCallback) { + $msg = call_user_func_array($this->_msgCallback, + array(&$this, $err)); + $err['message'] = $msg; + } + $push = $log = true; + $die = false; + // try the overriding callback first + $callback = $this->staticPopCallback(); + if ($callback) { + $this->staticPushCallback($callback); + } + if (!is_callable($callback)) { + // try the local callback next + $callback = $this->popCallback(); + if (is_callable($callback)) { + $this->pushCallback($callback); + } else { + // try the default callback + $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ? + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] : + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*']; + } + } + if (is_callable($callback)) { + switch(call_user_func($callback, $err)){ + case PEAR_ERRORSTACK_IGNORE: + return $err; + break; + case PEAR_ERRORSTACK_PUSH: + $log = false; + break; + case PEAR_ERRORSTACK_LOG: + $push = false; + break; + case PEAR_ERRORSTACK_DIE: + $die = true; + break; + // anything else returned has the same effect as pushandlog + } + } + if ($push) { + array_unshift($this->_errors, $err); + if (!isset($this->_errorsByLevel[$err['level']])) { + $this->_errorsByLevel[$err['level']] = array(); + } + $this->_errorsByLevel[$err['level']][] = &$this->_errors[0]; + } + if ($log) { + if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) { + $this->_log($err); + } + } + if ($die) { + die(); + } + if ($this->_compat && $push) { + return $this->raiseError($msg, $code, null, null, $err); + } + return $err; + } + + /** + * Static version of {@link push()} + * + * @param string $package Package name this error belongs to + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. see docs for {@link push()} + * @static + */ + function staticPush($package, $code, $level = 'error', $params = array(), + $msg = false, $repackage = false, $backtrace = false) + { + $s = &PEAR_ErrorStack::singleton($package); + if ($s->_contextCallback) { + if (!$backtrace) { + if (function_exists('debug_backtrace')) { + $backtrace = debug_backtrace(); + } + } + } + return $s->push($code, $level, $params, $msg, $repackage, $backtrace); + } + + /** + * Log an error using PEAR::Log + * @param array $err Error array + * @param array $levels Error level => Log constant map + * @access protected + */ + function _log($err) + { + if ($this->_logger) { + $logger = &$this->_logger; + } else { + $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']; + } + if (is_a($logger, 'Log')) { + $levels = array( + 'exception' => PEAR_LOG_CRIT, + 'alert' => PEAR_LOG_ALERT, + 'critical' => PEAR_LOG_CRIT, + 'error' => PEAR_LOG_ERR, + 'warning' => PEAR_LOG_WARNING, + 'notice' => PEAR_LOG_NOTICE, + 'info' => PEAR_LOG_INFO, + 'debug' => PEAR_LOG_DEBUG); + if (isset($levels[$err['level']])) { + $level = $levels[$err['level']]; + } else { + $level = PEAR_LOG_INFO; + } + $logger->log($err['message'], $level, $err); + } else { // support non-standard logs + call_user_func($logger, $err); + } + } + + + /** + * Pop an error off of the error stack + * + * @return false|array + * @since 0.4alpha it is no longer possible to specify a specific error + * level to return - the last error pushed will be returned, instead + */ + function pop() + { + $err = @array_shift($this->_errors); + if (!is_null($err)) { + @array_pop($this->_errorsByLevel[$err['level']]); + if (!count($this->_errorsByLevel[$err['level']])) { + unset($this->_errorsByLevel[$err['level']]); + } + } + return $err; + } + + /** + * Pop an error off of the error stack, static method + * + * @param string package name + * @return boolean + * @since PEAR1.5.0a1 + */ + function staticPop($package) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop(); + } + } + + /** + * Determine whether there are any errors on the stack + * @param string|array Level name. Use to determine if any errors + * of level (string), or levels (array) have been pushed + * @return boolean + */ + function hasErrors($level = false) + { + if ($level) { + return isset($this->_errorsByLevel[$level]); + } + return count($this->_errors); + } + + /** + * Retrieve all errors since last purge + * + * @param boolean set in order to empty the error stack + * @param string level name, to return only errors of a particular severity + * @return array + */ + function getErrors($purge = false, $level = false) + { + if (!$purge) { + if ($level) { + if (!isset($this->_errorsByLevel[$level])) { + return array(); + } else { + return $this->_errorsByLevel[$level]; + } + } else { + return $this->_errors; + } + } + if ($level) { + $ret = $this->_errorsByLevel[$level]; + foreach ($this->_errorsByLevel[$level] as $i => $unused) { + // entries are references to the $_errors array + $this->_errorsByLevel[$level][$i] = false; + } + // array_filter removes all entries === false + $this->_errors = array_filter($this->_errors); + unset($this->_errorsByLevel[$level]); + return $ret; + } + $ret = $this->_errors; + $this->_errors = array(); + $this->_errorsByLevel = array(); + return $ret; + } + + /** + * Determine whether there are any errors on a single error stack, or on any error stack + * + * The optional parameter can be used to test the existence of any errors without the need of + * singleton instantiation + * @param string|false Package name to check for errors + * @param string Level name to check for a particular severity + * @return boolean + * @static + */ + function staticHasErrors($package = false, $level = false) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + if ($obj->hasErrors($level)) { + return true; + } + } + return false; + } + + /** + * Get a list of all errors since last purge, organized by package + * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be + * @param boolean $purge Set to purge the error stack of existing errors + * @param string $level Set to a level name in order to retrieve only errors of a particular level + * @param boolean $merge Set to return a flat array, not organized by package + * @param array $sortfunc Function used to sort a merged array - default + * sorts by time, and should be good for most cases + * @static + * @return array + */ + function staticGetErrors($purge = false, $level = false, $merge = false, + $sortfunc = array('PEAR_ErrorStack', '_sortErrors')) + { + $ret = array(); + if (!is_callable($sortfunc)) { + $sortfunc = array('PEAR_ErrorStack', '_sortErrors'); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level); + if ($test) { + if ($merge) { + $ret = array_merge($ret, $test); + } else { + $ret[$package] = $test; + } + } + } + if ($merge) { + usort($ret, $sortfunc); + } + return $ret; + } + + /** + * Error sorting function, sorts by time + * @access private + */ + function _sortErrors($a, $b) + { + if ($a['time'] == $b['time']) { + return 0; + } + if ($a['time'] < $b['time']) { + return 1; + } + return -1; + } + + /** + * Standard file/line number/function/class context callback + * + * This function uses a backtrace generated from {@link debug_backtrace()} + * and so will not work at all in PHP < 4.3.0. The frame should + * reference the frame that contains the source of the error. + * @return array|false either array('file' => file, 'line' => line, + * 'function' => function name, 'class' => class name) or + * if this doesn't work, then false + * @param unused + * @param integer backtrace frame. + * @param array Results of debug_backtrace() + * @static + */ + function getFileLine($code, $params, $backtrace = null) + { + if ($backtrace === null) { + return false; + } + $frame = 0; + $functionframe = 1; + if (!isset($backtrace[1])) { + $functionframe = 0; + } else { + while (isset($backtrace[$functionframe]['function']) && + $backtrace[$functionframe]['function'] == 'eval' && + isset($backtrace[$functionframe + 1])) { + $functionframe++; + } + } + if (isset($backtrace[$frame])) { + if (!isset($backtrace[$frame]['file'])) { + $frame++; + } + $funcbacktrace = $backtrace[$functionframe]; + $filebacktrace = $backtrace[$frame]; + $ret = array('file' => $filebacktrace['file'], + 'line' => $filebacktrace['line']); + // rearrange for eval'd code or create function errors + if (strpos($filebacktrace['file'], '(') && + preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'], + $matches)) { + $ret['file'] = $matches[1]; + $ret['line'] = $matches[2] + 0; + } + if (isset($funcbacktrace['function']) && isset($backtrace[1])) { + if ($funcbacktrace['function'] != 'eval') { + if ($funcbacktrace['function'] == '__lambda_func') { + $ret['function'] = 'create_function() code'; + } else { + $ret['function'] = $funcbacktrace['function']; + } + } + } + if (isset($funcbacktrace['class']) && isset($backtrace[1])) { + $ret['class'] = $funcbacktrace['class']; + } + return $ret; + } + return false; + } + + /** + * Standard error message generation callback + * + * This method may also be called by a custom error message generator + * to fill in template values from the params array, simply + * set the third parameter to the error message template string to use + * + * The special variable %__msg% is reserved: use it only to specify + * where a message passed in by the user should be placed in the template, + * like so: + * + * Error message: %msg% - internal error + * + * If the message passed like so: + * + * <code> + * $stack->push(ERROR_CODE, 'error', array(), 'server error 500'); + * </code> + * + * The returned error message will be "Error message: server error 500 - + * internal error" + * @param PEAR_ErrorStack + * @param array + * @param string|false Pre-generated error message template + * @static + * @return string + */ + function getErrorMessage(&$stack, $err, $template = false) + { + if ($template) { + $mainmsg = $template; + } else { + $mainmsg = $stack->getErrorMessageTemplate($err['code']); + } + $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg); + if (is_array($err['params']) && count($err['params'])) { + foreach ($err['params'] as $name => $val) { + if (is_array($val)) { + // @ is needed in case $val is a multi-dimensional array + $val = @implode(', ', $val); + } + if (is_object($val)) { + if (method_exists($val, '__toString')) { + $val = $val->__toString(); + } else { + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING, + 'warning', array('obj' => get_class($val)), + 'object %obj% passed into getErrorMessage, but has no __toString() method'); + $val = 'Object'; + } + } + $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg); + } + } + return $mainmsg; + } + + /** + * Standard Error Message Template generator from code + * @return string + */ + function getErrorMessageTemplate($code) + { + if (!isset($this->_errorMsgs[$code])) { + return '%__msg%'; + } + return $this->_errorMsgs[$code]; + } + + /** + * Set the Error Message Template array + * + * The array format must be: + * <pre> + * array(error code => 'message template',...) + * </pre> + * + * Error message parameters passed into {@link push()} will be used as input + * for the error message. If the template is 'message %foo% was %bar%', and the + * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will + * be 'message one was six' + * @return string + */ + function setErrorMessageTemplate($template) + { + $this->_errorMsgs = $template; + } + + + /** + * emulate PEAR::raiseError() + * + * @return PEAR_Error + */ + function raiseError() + { + require_once 'PEAR.php'; + $args = func_get_args(); + return call_user_func_array(array('PEAR', 'raiseError'), $args); + } +} +$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack'); +$stack->pushCallback(array('PEAR_ErrorStack', '_handleError')); +?> diff --git a/includes/pear/PEAR/ErrorStack5.php b/includes/pear/PEAR/ErrorStack5.php new file mode 100644 index 0000000..52714d5 --- /dev/null +++ b/includes/pear/PEAR/ErrorStack5.php @@ -0,0 +1,921 @@ +<?php +/** + * Error Stack Implementation - E_STRICT compliant + * + * This is an incredibly simple implementation of a very complex error handling + * facility. It contains the ability + * to track multiple errors from multiple packages simultaneously. In addition, + * it can track errors of many levels, save data along with the error, context + * information such as the exact file, line number, class and function that + * generated the error, and if necessary, it can raise a traditional PEAR_Error. + * It has built-in support for PEAR::Log, to log errors as they occur + * @author Greg Beaver <cellog@php.net> + * @version 0.1alpha (E_STRICT) + * @package PEAR_ErrorStack + * @category Debugging + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * PEAR_Exception is used by default + */ +require_once 'PEAR/Exception.php'; + +class PEAR_ErrorStack_Exception extends PEAR_Exception{} + +/**#@+ + * One of four possible return values from the error Callback + * @see PEAR_ErrorStack::_errorCallback() + */ +/** + * If this is returned, then the error will be both pushed onto the stack + * and logged. + */ +define('PEAR_ERRORSTACK_PUSHANDLOG', 1); +/** + * If this is returned, then the error will only be pushed onto the stack, + * and not logged. + */ +define('PEAR_ERRORSTACK_PUSH', 2); +/** + * If this is returned, then the error will only be logged, but not pushed + * onto the error stack. + */ +define('PEAR_ERRORSTACK_LOG', 3); +/** + * If this is returned, then the error is completely ignored. + */ +define('PEAR_ERRORSTACK_IGNORE', 4); +/**#@-*/ + +/** + * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in + * the singleton method. + */ +define('PEAR_ERRORSTACK_ERR_NONCLASS', 1); + +/** + * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()} + * that has no __toString() method + */ +define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2); +/** + * Error Stack Implementation + * + * Usage: + * <code> + * // global error stack + * $global_stack = &PEAR_ErrorStack::singleton('MyPackage'); + * // local error stack + * $local_stack = new PEAR_ErrorStack('MyPackage'); + * </code> + * @copyright 2004 Gregory Beaver <cellog@php.net> + * @package PEAR_ErrorStack + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ +class PEAR_ErrorStack { + /** + * Singleton storage + * + * Format: + * <pre> + * array( + * 'package1' => PEAR_ErrorStack object, + * 'package2' => PEAR_ErrorStack object, + * ... + * ) + * </pre> + */ + static protected $singleton; + + /** + * Global error callback (default) + * + * This is only used if set to non-false. * is the default callback for + * all packages, whereas specific packages may set a default callback + * for all instances, regardless of whether they are a singleton or not. + * + * To exclude non-singletons, only set the local callback for the singleton + * @see PEAR_ErrorStack::setDefaultCallback() + */ + static protected $globalcallback = + array( + '*' => false, + ); + + /** + * Global Log object (default) + * + * This is only used if set to non-false. Use to set a default log object for + * all stacks, regardless of instantiation order or location + * @see PEAR_ErrorStack::setDefaultLogger() + */ + static protected $globallogger = false; + + /** + * PEAR_Warning callback + * + * This is only used if set to non-false. Use to set a default log object for + * all stacks, regardless of instantiation order or location + * @see PEAR_ErrorStack::setDefaultLogger() + */ + static protected $pearwarningcallback = false; + + /** + * Global Overriding Callback + * + * This callback will override any error callbacks that specific loggers have set. + * Use with EXTREME caution + * @see PEAR_ErrorStack::staticPushCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ + static protected $overridecallback = array(); + + /** + * Errors are stored in the order that they are pushed on the stack. + * @since 0.4alpha Errors are no longer organized by error level. + * This renders pop() nearly unusable, and levels could be more easily + * handled in a callback anyway + * @var array + */ + private $_errors = array(); + + /** + * Storage of errors by level. + * + * Allows easy retrieval and deletion of only errors from a particular level + * @since PEAR 1.4.0dev + * @var array + */ + private $_errorsByLevel = array(); + + /** + * Package name this error stack represents + * @var string + */ + protected $_package; + + /** + * Determines whether a PEAR_Error is thrown upon every error addition + * @var boolean + */ + private $_compat = false; + + /** + * If set to a valid callback, this will be used to generate the error + * message from the error code, otherwise the message passed in will be + * used + * @var false|string|array + */ + private $_msgCallback = false; + + /** + * If set to a valid callback, this will be used to generate the error + * context for an error. For PHP-related errors, this will be a file + * and line number as retrieved from debug_backtrace(), but can be + * customized for other purposes. The error might actually be in a separate + * configuration file, or in a database query. + * @var false|string|array + */ + public $contextCallback = false; + + /** + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one an PEAR_ERRORSTACK_* constant + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @var false|string|array + */ + protected $_errorCallback = array(); + + /** + * PEAR::Log object for logging errors + * @var false|Log + */ + protected $_logger = false; + + /** + * Error messages - designed to be overridden + * @var array + * @abstract + */ + protected $_errorMsgs = array(); + + /** + * Set up a new error stack + * + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error (ignored) + */ + public function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false) + { + $this->_package = $package; + $this->setMessageCallback($msgCallback); + $this->setContextCallback($contextCallback); + $this->_compat = false; + } + + /** + * Return a single error stack for this package. + * + * Note that all parameters are ignored if the stack for package $package + * has already been instantiated + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + * @param string $exceptionClass exception class to instantiate if + * in PHP 5 + * @param string $stackClass class to instantiate + * @static + * @return PEAR_ErrorStack + */ + static public function singleton($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack') + { + if (isset(self::$singleton[$package])) { + return self::$singleton[$package]; + } + if (!class_exists($stackClass)) { + throw PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS, + 'exception', array('stackclass' => $stackClass), + 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)', + false, $trace); + } + return self::$singleton[$package] = + new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error); + } + + + /** + * Set up a PEAR::Log object for all error stacks that don't have one + * @param Log $log + * @static + */ + static public function setDefaultLogger($log) + { + self::$globallogger = $log; + } + + /** + * Set up a PEAR::Log object for this error stack + * @param Log $log + */ + public function setLogger($log) + { + $this->_logger = $log; + } + + /** + * Set an error code => error message mapping callback + * + * This method sets the callback that can be used to generate error + * messages for any instance + * @param array|string Callback function/method + */ + public function setMessageCallback($msgCallback) + { + if (!$msgCallback) { + $this->_msgCallback = array($this, 'getErrorMessage'); + } else { + if (is_callable($msgCallback)) { + $this->_msgCallback = $msgCallback; + } + } + } + + /** + * Get an error code => error message mapping callback + * + * This method returns the current callback that can be used to generate error + * messages + * @return array|string|false Callback function/method or false if none + */ + public function getMessageCallback() + { + return $this->_msgCallback; + } + + /** + * Sets a default callback to be used by all error stacks + * + * This method sets the callback that can be used to generate error + * messages for a singleton + * @param array|string Callback function/method + * @param string Package name, or false for all packages + */ + static public function setDefaultCallback($callback = false, $package = false) + { + if (!is_callable($callback)) { + $callback = false; + } + $package = $package ? $package : '*'; + self::$globalcallback[$package] = $callback; + } + + /** + * Do not use + * @access private + */ + static public function setPEARWarningCallback($callback) + { + self::$pearwarningcallback = $callback; + } + + /** + * Set an error code => error message mapping callback + * + * This method sets the callback that can be used to generate context + * information for an error. Passing in NULL will disable context generation + * and remove the expensive call to debug_backtrace() + * @param array|string Callback function/method + */ + public function setContextCallback($contextCallback) + { + if ($contextCallback === null) { + return $this->contextCallback = false; + } + if (!$contextCallback) { + $this->contextCallback = array($this, 'getFileLine'); + } else { + if (is_callable($contextCallback)) { + $this->contextCallback = $contextCallback; + } + } + } + + /** + * Set an error Callback + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one of the ERRORSTACK_* constants. + * + * This functionality can be used to emulate PEAR's pushErrorHandling, and + * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of + * the error stack or logging + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see popCallback() + * @param string|array $cb + */ + public function pushCallback($cb) + { + array_push($this->_errorCallback, $cb); + } + + /** + * Remove a callback from the error callback stack + * @see pushCallback() + * @return array|string|false + */ + public function popCallback() + { + if (!count($this->_errorCallback)) { + return false; + } + return array_pop($this->_errorCallback); + } + + /** + * Set a temporary overriding error callback for every package error stack + * + * Use this to temporarily disable all existing callbacks (can be used + * to emulate the @ operator, for instance) + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see staticPopCallback(), pushCallback() + * @param string|array $cb + */ + static public function staticPushCallback($cb) + { + array_push(self::$overridecallback, $cb); + } + + /** + * Remove a temporary overriding error callback + * @see staticPushCallback() + * @return array|string|false + */ + static public function staticPopCallback() + { + $ret = array_pop(self::$overridecallback); + if (!is_array(self::$overridecallback)) { + self::$overridecallback = array(); + } + return $ret; + } + + /** + * demote an exception to a non-fatal error (default is warning) + * @param Exception + * @param string + */ + public function demoteException($e, $level = 'warning') + { + $this->push(get_class($e), $level, array(), + $e->getMessage(), $e, $e->getTrace()); + } + + /** + * promote a warning into a PEAR_Exception + * + * It is probably best to do this manually, to take advantage of the + * use of exception classnames to categorize errors + * @return PEAR_Exception + */ + public function promoteWarning($warning, $exceptionclass = 'PEAR_Exception') + { + return new $exceptionclass($warning['message'], + array($warning), $warning['code']); + } + + /** + * Add an error to the stack + * + * If the message generator exists, it is called with 2 parameters. + * - the current Error Stack object + * - an array that is in the same format as an error. Available indices + * are 'code', 'package', 'time', 'params', 'level', and 'context' + * + * Next, if the error should contain context information, this is + * handled by the context grabbing method. + * Finally, the error is pushed onto the proper error stack + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array|Exception + * if compatibility mode is on, a PEAR_Error is also + * thrown. If the class Exception exists, then one + * is returned to allow code like: + * <code> + * throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob'))); + * </code> + * + * The errorData property of the exception class will be set to the array + * that would normally be returned. If a PEAR_Error is returned, the userinfo + * property is set to the array + * + * Otherwise, an array is returned in this format: + * <code> + * array( + * 'code' => $code, + * 'params' => $params, + * 'package' => $this->_package, + * 'level' => $level, + * 'time' => time(), + * 'context' => $context, + * 'message' => $msg, + * //['repackage' => $err] repackaged error array + * ); + * </code> + */ + public function push($code, $level = 'error', $params = array(), $msg = false, + $repackage = false, $backtrace = false) + { + $context = false; + // grab error context + if ($this->contextCallback) { + if (!$backtrace) { + $backtrace = debug_backtrace(); + } + $context = call_user_func($this->contextCallback, $code, $params, $backtrace); + } + + // save error + $time = explode(' ', microtime()); + $time = $time[1] + $time[0]; + $err = array( + 'code' => $code, + 'params' => $params, + 'package' => $this->_package, + 'level' => $level, + 'time' => $time, + 'context' => $context, + 'message' => $msg, + ); + + // set up the error message, if necessary + if ($this->_msgCallback) { + $msg = call_user_func_array($this->_msgCallback, + array($this, $err)); + $err['message'] = $msg; + } + + + if ($repackage) { + $err['repackage'] = $repackage; + } + $push = $log = true; + // try the overriding callback first + $callback = $this->staticPopCallback(); + if ($callback) { + $this->staticPushCallback($callback); + } + if (!is_callable($callback)) { + // try the local callback next + $callback = $this->popCallback(); + if (is_callable($callback)) { + $this->pushCallback($callback); + } else { + // try the default callback + $callback = isset(self::$globalcallback[$this->_package]) ? + self::$globalcallback[$this->_package] : + self::$globalcallback['*']; + } + } + if (is_callable($callback)) { + switch(call_user_func($callback, $err)){ + case PEAR_ERRORSTACK_IGNORE: + return $err; + break; + case PEAR_ERRORSTACK_PUSH: + $log = false; + break; + case PEAR_ERRORSTACK_LOG: + $push = false; + break; + // anything else returned has the same effect as pushandlog + } + } + if ($push) { + if (is_callable(self::$pearwarningcallback)) { + call_user_func(self::$pearwarningcallback, $err); + } + array_unshift($this->_errors, $err); + $this->_errorsByLevel[$err['level']][] = &$this->_errors[0]; + } + if ($log) { + if ($this->_logger || self::$globallogger) { + $this->_log($err); + } + } + return $err; + } + + /** + * Static version of {@link push()} + * + * @param string $package Package name this error belongs to + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|null|Exception + * if compatibility mode is on, a PEAR_Error is also + * thrown. If the class Exception exists, then one + * is returned to allow code like: + * <code> + * throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob'))); + * </code> + * @static + */ + static public function staticPush($package, $code, $level = 'error', $params = array(), + $msg = false, $repackage = false, $backtrace = false) + { + $s = PEAR_ErrorStack::singleton($package); + if ($s->contextCallback) { + if (!$backtrace) { + $backtrace = debug_backtrace(); + } + } + return $s->push($code, $level, $params, $msg, $repackage, $backtrace); + } + + /** + * Log an error using PEAR::Log + * @param array $err Error array + * @param array $levels Error level => Log constant map + * @access protected + */ + protected function _log($err, $levels = array( + 'exception' => PEAR_LOG_CRIT, + 'alert' => PEAR_LOG_ALERT, + 'critical' => PEAR_LOG_CRIT, + 'error' => PEAR_LOG_ERR, + 'warning' => PEAR_LOG_WARNING, + 'notice' => PEAR_LOG_NOTICE, + 'info' => PEAR_LOG_INFO, + 'debug' => PEAR_LOG_DEBUG)) + { + if (isset($levels[$err['level']])) { + $level = $levels[$err['level']]; + } else { + $level = PEAR_LOG_INFO; + } + if ($this->_logger) { + $this->_logger->log($err['message'], $level, $err); + } else { + self::$globallogger->log($err['message'], $level, $err); + } + } + + + /** + * Pop an error off of the error stack + * + * @return false|array + * @since 0.4alpha it is no longer possible to specify a specific error + * level to return - the last error pushed will be returned, instead + */ + public function pop() + { + return @array_shift($this->_errors); + } + + /** + * Determine whether there are any errors on the stack + * @param string|array Level name. Use to determine if any errors + * of level (string), or levels (array) have been pushed + * @return boolean + */ + public function hasErrors($level = false) + { + if ($level) { + return isset($this->_errorsByLevel[$level]); + } + return count($this->_errors); + } + + /** + * Retrieve all errors since last purge + * + * @param boolean set in order to empty the error stack + * @param string level name, to return only errors of a particular severity + * @return array + */ + public function getErrors($purge = false, $level = false) + { + if (!$purge) { + if ($level) { + if (!isset($this->_errorsByLevel[$level])) { + return array(); + } else { + return $this->_errorsByLevel[$level]; + } + } else { + return $this->_errors; + } + } + if ($level) { + $ret = $this->_errorsByLevel[$level]; + foreach ($this->_errorsByLevel[$level] as $i => $unused) { + // entries are references to the $_errors array + $this->_errorsByLevel[$level][$i] = false; + } + // array_filter removes all entries === false + $this->_errors = array_filter($this->_errors); + unset($this->_errorsByLevel[$level]); + return $ret; + } + $ret = $this->_errors; + $this->_errors = array(); + $this->_errorsByLevel = array(); + return $ret; + } + + /** + * Determine whether there are any errors on a single error stack, or on any error stack + * + * The optional parameter can be used to test the existence of any errors without the need of + * singleton instantiation + * @param string|false Package name to check for errors + * @param string Level name to check for a particular severity + * @return boolean + */ + static public function staticHasErrors($package = false, $level = false) + { + if ($package) { + if (!isset(self::$singleton[$package])) { + return false; + } + return self::$singleton[$package]->hasErrors($level); + } + foreach (self::$singleton as $package => $obj) { + if ($obj->hasErrors($level)) { + return true; + } + } + return false; + } + + /** + * Get a list of all errors since last purge, organized by package + * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be + * @param boolean $clearStack Set to purge the error stack of existing errors + * @param string $level Set to a level name in order to retrieve only errors of a particular level + * @param boolean $merge Set to return a flat array, not organized by package + * @param array $sortfunc Function used to sort a merged array - default + * sorts by time, and should be good for most cases + * @return array + */ + static public function staticGetErrors($purge = false, $level = false, $merge = false, $sortfunc = array('PEAR_ErrorStack', '_sortErrors')) + { + $ret = array(); + if (!is_callable($sortfunc)) { + $sortfunc = array('PEAR_ErrorStack', '_sortErrors'); + } + foreach (self::$singleton as $package => $obj) { + $test = self::$singleton[$package]->getErrors($purge, $level); + if ($test) { + if ($merge) { + $ret = array_merge($ret, $test); + } else { + $ret[$package] = $test; + } + } + } + if ($merge) { + usort($ret, $sortfunc); + } + return $ret; + } + + /** + * Error sorting function, sorts by time + * @access private + */ + function _sortErrors($a, $b) + { + if ($a['time'] == $b['time']) { + return 0; + } + if ($a['time'] < $b['time']) { + return 1; + } + return -1; + } + + /** + * Standard file/line number/function/class context callback + * + * This function uses a backtrace generated from {@link debug_backtrace()} + * and so will not work at all in PHP < 4.3.0. The frame should + * reference the frame that contains the source of the error. + * @return array|false either array('file' => file, 'line' => line, + * 'function' => function name, 'class' => class name) or + * if this doesn't work, then false + * @param unused + * @param integer backtrace frame. + * @param array Results of debug_backtrace() + */ + static public function getFileLine($code, $params, $backtrace = null) + { + if ($backtrace === null) { + return false; + } + $frame = 0; + $functionframe = 1; + if (!isset($backtrace[1])) { + $functionframe = 0; + } else { + while (isset($backtrace[$functionframe]['function']) && + $backtrace[$functionframe]['function'] == 'eval' && + isset($backtrace[$functionframe + 1])) { + $functionframe++; + } + } + if (isset($backtrace[$frame])) { + if (!isset($backtrace[$frame]['file'])) { + $frame++; + } + $funcbacktrace = $backtrace[$functionframe]; + $filebacktrace = $backtrace[$frame]; + $ret = array('file' => $filebacktrace['file'], + 'line' => $filebacktrace['line']); + // rearrange for eval'd code or create function errors + if (strpos($filebacktrace['file'], '(') && + preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'], + $matches)) { + $ret['file'] = $matches[1]; + $ret['line'] = $matches[2] + 0; + } + if (isset($funcbacktrace['function']) && isset($backtrace[1])) { + if ($funcbacktrace['function'] != 'eval') { + if ($funcbacktrace['function'] == '__lambda_func') { + $ret['function'] = 'create_function() code'; + } else { + $ret['function'] = $funcbacktrace['function']; + } + } + } + if (isset($funcbacktrace['class']) && isset($backtrace[1])) { + $ret['class'] = $funcbacktrace['class']; + } + return $ret; + } + return false; + } + + /** + * Standard error message generation callback + * + * This method may also be called by a custom error message generator + * to fill in template values from the params array, simply + * set the third parameter to the error message template string to use + * + * The special variable %__msg% is reserved: use it only to specify + * where a message passed in by the user should be placed in the template, + * like so: + * + * Error message: %msg% - internal error + * + * If the message passed like so: + * + * <code> + * $stack->push(ERROR_CODE, 'error', array(), 'server error 500'); + * </code> + * + * The returned error message will be "Error message: server error 500 - + * internal error" + * @param PEAR_ErrorStack + * @param array + * @param string|false Pre-generated error message template + * @static + * @return string + */ + public function getErrorMessage(&$stack, $err, $template = false) + { + if ($template) { + $mainmsg = $template; + } else { + $mainmsg = $stack->getErrorMessageTemplate($err['code']); + } + $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg); + if (count($err['params'])) { + foreach ($err['params'] as $name => $val) { + if (is_array($val)) { + // @ is needed in case $val is a multi-dimensional array + $val = @implode(', ', $val); + } + if (is_object($val)) { + if (method_exists($val, '__toString')) { + $val = $val->__toString(); + } else { + throw PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING, + 'warning', array('obj' => get_class($val)), + 'object %obj% passed into getErrorMessage, but has no __toString() method'); + $val = 'Object'; + } + } + $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg); + } + } + return $mainmsg; + } + + /** + * Standard Error Message Template generator from code + * @return string + */ + public function getErrorMessageTemplate($code) + { + if (!isset($this->_errorMsgs[$code])) { + return '%__msg%'; + } + return $this->_errorMsgs[$code]; + } + + /** + * Set the Error Message Template array + * + * The array format must be: + * <pre> + * array(error code => 'message template',...) + * </pre> + * + * Error message parameters passed into {@link push()} will be used as input + * for the error message. If the template is 'message %foo% was %bar%', and the + * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will + * be 'message one was six' + * @return string + */ + public function setErrorMessageTemplate($template) + { + $this->_errorMsgs = $template; + } +} +PEAR_ErrorStack::singleton('PEAR_ErrorStack', false, null, false, 'PEAR_ErrorStack_Exception'); +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Exception.php b/includes/pear/PEAR/Exception.php new file mode 100644 index 0000000..85eaf2c --- /dev/null +++ b/includes/pear/PEAR/Exception.php @@ -0,0 +1,389 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ +/** + * PEAR_Exception + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR_Exception + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Hans Lellelid <hans@velum.net> + * @author Bertrand Mansion <bmansion@mamasam.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR_Exception + * @since File available since Release 1.0.0 + */ + + +/** + * Base PEAR_Exception Class + * + * 1) Features: + * + * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception)) + * - Definable triggers, shot when exceptions occur + * - Pretty and informative error messages + * - Added more context info available (like class, method or cause) + * - cause can be a PEAR_Exception or an array of mixed + * PEAR_Exceptions/PEAR_ErrorStack warnings + * - callbacks for specific exception classes and their children + * + * 2) Ideas: + * + * - Maybe a way to define a 'template' for the output + * + * 3) Inherited properties from PHP Exception Class: + * + * protected $message + * protected $code + * protected $line + * protected $file + * private $trace + * + * 4) Inherited methods from PHP Exception Class: + * + * __clone + * __construct + * getMessage + * getCode + * getFile + * getLine + * getTraceSafe + * getTraceSafeAsString + * __toString + * + * 5) Usage example + * + * <code> + * require_once 'PEAR/Exception.php'; + * + * class Test { + * function foo() { + * throw new PEAR_Exception('Error Message', ERROR_CODE); + * } + * } + * + * function myLogger($pear_exception) { + * echo $pear_exception->getMessage(); + * } + * // each time a exception is thrown the 'myLogger' will be called + * // (its use is completely optional) + * PEAR_Exception::addObserver('myLogger'); + * $test = new Test; + * try { + * $test->foo(); + * } catch (PEAR_Exception $e) { + * print $e; + * } + * </code> + * + * @category pear + * @package PEAR_Exception + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Hans Lellelid <hans@velum.net> + * @author Bertrand Mansion <bmansion@mamasam.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR_Exception + * @since Class available since Release 1.0.0 + * + */ +class PEAR_Exception extends Exception +{ + const OBSERVER_PRINT = -2; + const OBSERVER_TRIGGER = -4; + const OBSERVER_DIE = -8; + protected $cause; + private static $_observers = array(); + private static $_uniqueid = 0; + private $_trace; + + /** + * Supported signatures: + * - PEAR_Exception(string $message); + * - PEAR_Exception(string $message, int $code); + * - PEAR_Exception(string $message, Exception $cause); + * - PEAR_Exception(string $message, Exception $cause, int $code); + * - PEAR_Exception(string $message, PEAR_Error $cause); + * - PEAR_Exception(string $message, PEAR_Error $cause, int $code); + * - PEAR_Exception(string $message, array $causes); + * - PEAR_Exception(string $message, array $causes, int $code); + * @param string exception message + * @param int|Exception|PEAR_Error|array|null exception cause + * @param int|null exception code or null + */ + public function __construct($message, $p2 = null, $p3 = null) + { + if (is_int($p2)) { + $code = $p2; + $this->cause = null; + } elseif (is_object($p2) || is_array($p2)) { + // using is_object allows both Exception and PEAR_Error + if (is_object($p2) && !($p2 instanceof Exception)) { + if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) { + throw new PEAR_Exception('exception cause must be Exception, ' . + 'array, or PEAR_Error'); + } + } + $code = $p3; + if (is_array($p2) && isset($p2['message'])) { + // fix potential problem of passing in a single warning + $p2 = array($p2); + } + $this->cause = $p2; + } else { + $code = null; + $this->cause = null; + } + parent::__construct($message, $code); + $this->signal(); + } + + /** + * @param mixed $callback - A valid php callback, see php func is_callable() + * - A PEAR_Exception::OBSERVER_* constant + * - An array(const PEAR_Exception::OBSERVER_*, + * mixed $options) + * @param string $label The name of the observer. Use this if you want + * to remove it later with removeObserver() + */ + public static function addObserver($callback, $label = 'default') + { + self::$_observers[$label] = $callback; + } + + public static function removeObserver($label = 'default') + { + unset(self::$_observers[$label]); + } + + /** + * @return int unique identifier for an observer + */ + public static function getUniqueId() + { + return self::$_uniqueid++; + } + + private function signal() + { + foreach (self::$_observers as $func) { + if (is_callable($func)) { + call_user_func($func, $this); + continue; + } + settype($func, 'array'); + switch ($func[0]) { + case self::OBSERVER_PRINT : + $f = (isset($func[1])) ? $func[1] : '%s'; + printf($f, $this->getMessage()); + break; + case self::OBSERVER_TRIGGER : + $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE; + trigger_error($this->getMessage(), $f); + break; + case self::OBSERVER_DIE : + $f = (isset($func[1])) ? $func[1] : '%s'; + die(printf($f, $this->getMessage())); + break; + default: + trigger_error('invalid observer type', E_USER_WARNING); + } + } + } + + /** + * Return specific error information that can be used for more detailed + * error messages or translation. + * + * This method may be overridden in child exception classes in order + * to add functionality not present in PEAR_Exception and is a placeholder + * to define API + * + * The returned array must be an associative array of parameter => value like so: + * <pre> + * array('name' => $name, 'context' => array(...)) + * </pre> + * @return array + */ + public function getErrorData() + { + return array(); + } + + /** + * Returns the exception that caused this exception to be thrown + * @access public + * @return Exception|array The context of the exception + */ + public function getCause() + { + return $this->cause; + } + + /** + * Function must be public to call on caused exceptions + * @param array + */ + public function getCauseMessage(&$causes) + { + $trace = $this->getTraceSafe(); + $cause = array('class' => get_class($this), + 'message' => $this->message, + 'file' => 'unknown', + 'line' => 'unknown'); + if (isset($trace[0])) { + if (isset($trace[0]['file'])) { + $cause['file'] = $trace[0]['file']; + $cause['line'] = $trace[0]['line']; + } + } + $causes[] = $cause; + if ($this->cause instanceof PEAR_Exception) { + $this->cause->getCauseMessage($causes); + } elseif ($this->cause instanceof Exception) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => $this->cause->getFile(), + 'line' => $this->cause->getLine()); + } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); + } elseif (is_array($this->cause)) { + foreach ($this->cause as $cause) { + if ($cause instanceof PEAR_Exception) { + $cause->getCauseMessage($causes); + } elseif ($cause instanceof Exception) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => $cause->getFile(), + 'line' => $cause->getLine()); + } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); + } elseif (is_array($cause) && isset($cause['message'])) { + // PEAR_ErrorStack warning + $causes[] = array( + 'class' => $cause['package'], + 'message' => $cause['message'], + 'file' => isset($cause['context']['file']) ? + $cause['context']['file'] : + 'unknown', + 'line' => isset($cause['context']['line']) ? + $cause['context']['line'] : + 'unknown', + ); + } + } + } + } + + public function getTraceSafe() + { + if (!isset($this->_trace)) { + $this->_trace = $this->getTrace(); + if (empty($this->_trace)) { + $backtrace = debug_backtrace(); + $this->_trace = array($backtrace[count($backtrace)-1]); + } + } + return $this->_trace; + } + + public function getErrorClass() + { + $trace = $this->getTraceSafe(); + return $trace[0]['class']; + } + + public function getErrorMethod() + { + $trace = $this->getTraceSafe(); + return $trace[0]['function']; + } + + public function __toString() + { + if (isset($_SERVER['REQUEST_URI'])) { + return $this->toHtml(); + } + return $this->toText(); + } + + public function toHtml() + { + $trace = $this->getTraceSafe(); + $causes = array(); + $this->getCauseMessage($causes); + $html = '<table style="border: 1px" cellspacing="0">' . "\n"; + foreach ($causes as $i => $cause) { + $html .= '<tr><td colspan="3" style="background: #ff9999">' + . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: ' + . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> ' + . 'on line <b>' . $cause['line'] . '</b>' + . "</td></tr>\n"; + } + $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n" + . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>' + . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>' + . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n"; + + foreach ($trace as $k => $v) { + $html .= '<tr><td style="text-align: center;">' . $k . '</td>' + . '<td>'; + if (!empty($v['class'])) { + $html .= $v['class'] . $v['type']; + } + $html .= $v['function']; + $args = array(); + if (!empty($v['args'])) { + foreach ($v['args'] as $arg) { + if (is_null($arg)) $args[] = 'null'; + elseif (is_array($arg)) $args[] = 'Array'; + elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')'; + elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false'; + elseif (is_int($arg) || is_double($arg)) $args[] = $arg; + else { + $arg = (string)$arg; + $str = htmlspecialchars(substr($arg, 0, 16)); + if (strlen($arg) > 16) $str .= '…'; + $args[] = "'" . $str . "'"; + } + } + } + $html .= '(' . implode(', ',$args) . ')' + . '</td>' + . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown') + . ':' . (isset($v['line']) ? $v['line'] : 'unknown') + . '</td></tr>' . "\n"; + } + $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>' + . '<td>{main}</td>' + . '<td> </td></tr>' . "\n" + . '</table>'; + return $html; + } + + public function toText() + { + $causes = array(); + $this->getCauseMessage($causes); + $causeMsg = ''; + foreach ($causes as $i => $cause) { + $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': ' + . $cause['message'] . ' in ' . $cause['file'] + . ' on line ' . $cause['line'] . "\n"; + } + return $causeMsg . $this->getTraceAsString(); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/FixPHP5PEARWarnings.php b/includes/pear/PEAR/FixPHP5PEARWarnings.php new file mode 100644 index 0000000..be5dc3c --- /dev/null +++ b/includes/pear/PEAR/FixPHP5PEARWarnings.php @@ -0,0 +1,7 @@ +<?php +if ($skipmsg) { + $a = &new $ec($code, $mode, $options, $userinfo); +} else { + $a = &new $ec($message, $code, $mode, $options, $userinfo); +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Frontend.php b/includes/pear/PEAR/Frontend.php new file mode 100644 index 0000000..b7447b5 --- /dev/null +++ b/includes/pear/PEAR/Frontend.php @@ -0,0 +1,228 @@ +<?php +/** + * PEAR_Frontend, the singleton-based frontend for user input/output + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +//require_once 'PEAR.php'; + +/** + * Which user interface class is being used. + * @var string class name + */ +$GLOBALS['_PEAR_FRONTEND_CLASS'] = 'PEAR_Frontend_CLI'; + +/** + * Instance of $_PEAR_Command_uiclass. + * @var object + */ +$GLOBALS['_PEAR_FRONTEND_SINGLETON'] = null; + +/** + * Singleton-based frontend for PEAR user input/output + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Frontend extends PEAR +{ + /** + * Retrieve the frontend object + * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk + * @static + */ + function &singleton($type = null) + { + if ($type === null) { + if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) { + $a = false; + return $a; + } + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + $a = PEAR_Frontend::setFrontendClass($type); + return $a; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to conform to the PEAR naming standard of + * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php) + * @param string $uiclass full class name + * @return PEAR_Frontend + * @static + */ + function &setFrontendClass($uiclass) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!class_exists($uiclass)) { + $file = str_replace('_', '/', $uiclass) . '.php'; + if (PEAR_Frontend::isIncludeable($file)) { + include_once $file; + } + } + + if (class_exists($uiclass)) { + $obj = &new $uiclass; + // quick test to see if this class implements a few of the most + // important frontend methods + if (is_a($obj, 'PEAR_Frontend')) { + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass; + return $obj; + } + + $err = PEAR::raiseError("not a frontend class: $uiclass"); + return $err; + } + + $err = PEAR::raiseError("no such class: $uiclass"); + return $err; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to be a descendant of PEAR_Frontend + * @param PEAR_Frontend + * @return PEAR_Frontend + * @static + */ + function &setFrontendObject($uiobject) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!is_a($uiobject, 'PEAR_Frontend')) { + $err = PEAR::raiseError('not a valid frontend class: (' . + get_class($uiobject) . ')'); + return $err; + } + + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject); + return $uiobject; + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $fp = @fopen($path, 'r', true); + if ($fp) { + fclose($fp); + return true; + } + + return false; + } + + /** + * @param PEAR_Config + */ + function setConfig(&$config) + { + } + + /** + * This can be overridden to allow session-based temporary file management + * + * By default, all files are deleted at the end of a session. The web installer + * needs to be able to sustain a list over many sessions in order to support + * user interaction with install scripts + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Log an action + * + * @param string $msg the message to log + * @param boolean $append_crlf + * @return boolean true + * @abstract + */ + function log($msg, $append_crlf = true) + { + } + + /** + * Run a post-installation script + * + * @param array $scripts array of post-install scripts + * @abstract + */ + function runPostinstallScripts(&$scripts) + { + } + + /** + * Display human-friendly output formatted depending on the + * $command parameter. + * + * This should be able to handle basic output data with no command + * @param mixed $data data structure containing the information to display + * @param string $command command from which this method was called + * @abstract + */ + function outputData($data, $command = '_default') + { + } + + /** + * Display a modal form dialog and return the given input + * + * A frontend that requires multiple requests to retrieve and process + * data must take these needs into account, and implement the request + * handling code. + * @param string $command command from which this method was called + * @param array $prompts associative array. keys are the input field names + * and values are the description + * @param array $types array of input field types (text, password, + * etc.) keys have to be the same like in $prompts + * @param array $defaults array of default values. again keys have + * to be the same like in $prompts. Do not depend + * on a default value being set. + * @return array input sent by the user + * @abstract + */ + function userDialog($command, $prompts, $types = array(), $defaults = array()) + { + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Frontend/CLI.php b/includes/pear/PEAR/Frontend/CLI.php new file mode 100644 index 0000000..8c52e4e --- /dev/null +++ b/includes/pear/PEAR/Frontend/CLI.php @@ -0,0 +1,751 @@ +<?php +/** + * PEAR_Frontend_CLI + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ +require_once 'PEAR/Frontend.php'; + +/** + * Command-line Frontend for the PEAR Installer + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Frontend_CLI extends PEAR_Frontend +{ + /** + * What type of user interface this frontend is for. + * @var string + * @access public + */ + var $type = 'CLI'; + var $lp = ''; // line prefix + + var $params = array(); + var $term = array( + 'bold' => '', + 'normal' => '', + ); + + function PEAR_Frontend_CLI() + { + parent::PEAR(); + $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1 + if (function_exists('posix_isatty') && !posix_isatty(1)) { + // output is being redirected to a file or through a pipe + } elseif ($term) { + if (preg_match('/^(xterm|vt220|linux)/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109); + $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109); + } elseif (preg_match('/^vt100/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0); + $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0); + } + } elseif (OS_WINDOWS) { + // XXX add ANSI codes here + } + } + + /** + * @param object PEAR_Error object + */ + function displayError($e) + { + return $this->_displayLine($e->getMessage()); + } + + /** + * @param object PEAR_Error object + */ + function displayFatalError($eobj) + { + $this->displayError($eobj); + if (class_exists('PEAR_Config')) { + $config = &PEAR_Config::singleton(); + if ($config->get('verbose') > 5) { + if (function_exists('debug_print_backtrace')) { + debug_print_backtrace(); + exit(1); + } + + $raised = false; + foreach (debug_backtrace() as $i => $frame) { + if (!$raised) { + if (isset($frame['class']) + && strtolower($frame['class']) == 'pear' + && strtolower($frame['function']) == 'raiseerror' + ) { + $raised = true; + } else { + continue; + } + } + + $frame['class'] = !isset($frame['class']) ? '' : $frame['class']; + $frame['type'] = !isset($frame['type']) ? '' : $frame['type']; + $frame['function'] = !isset($frame['function']) ? '' : $frame['function']; + $frame['line'] = !isset($frame['line']) ? '' : $frame['line']; + $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]"); + } + } + } + + exit(1); + } + + /** + * Instruct the runInstallScript method to skip a paramgroup that matches the + * id value passed in. + * + * This method is useful for dynamically configuring which sections of a post-install script + * will be run based on the user's setup, which is very useful for making flexible + * post-install scripts without losing the cross-Frontend ability to retrieve user input + * @param string + */ + function skipParamgroup($id) + { + $this->_skipSections[$id] = true; + } + + function runPostinstallScripts(&$scripts) + { + foreach ($scripts as $i => $script) { + $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj); + } + } + + /** + * @param array $xml contents of postinstallscript tag + * @param object $script post-installation script + * @param string install|upgrade + */ + function runInstallScript($xml, &$script) + { + $this->_skipSections = array(); + if (!is_array($xml) || !isset($xml['paramgroup'])) { + $script->run(array(), '_default'); + return; + } + + $completedPhases = array(); + if (!isset($xml['paramgroup'][0])) { + $xml['paramgroup'] = array($xml['paramgroup']); + } + + foreach ($xml['paramgroup'] as $group) { + if (isset($this->_skipSections[$group['id']])) { + // the post-install script chose to skip this section dynamically + continue; + } + + if (isset($group['name'])) { + $paramname = explode('::', $group['name']); + if ($lastgroup['id'] != $paramname[0]) { + continue; + } + + $group['name'] = $paramname[1]; + if (!isset($answers)) { + return; + } + + if (isset($answers[$group['name']])) { + switch ($group['conditiontype']) { + case '=' : + if ($answers[$group['name']] != $group['value']) { + continue 2; + } + break; + case '!=' : + if ($answers[$group['name']] == $group['value']) { + continue 2; + } + break; + case 'preg_match' : + if (!@preg_match('/' . $group['value'] . '/', + $answers[$group['name']])) { + continue 2; + } + break; + default : + return; + } + } + } + + $lastgroup = $group; + if (isset($group['instructions'])) { + $this->_display($group['instructions']); + } + + if (!isset($group['param'][0])) { + $group['param'] = array($group['param']); + } + + if (isset($group['param'])) { + if (method_exists($script, 'postProcessPrompts')) { + $prompts = $script->postProcessPrompts($group['param'], $group['id']); + if (!is_array($prompts) || count($prompts) != count($group['param'])) { + $this->outputData('postinstall', 'Error: post-install script did not ' . + 'return proper post-processed prompts'); + $prompts = $group['param']; + } else { + foreach ($prompts as $i => $var) { + if (!is_array($var) || !isset($var['prompt']) || + !isset($var['name']) || + ($var['name'] != $group['param'][$i]['name']) || + ($var['type'] != $group['param'][$i]['type']) + ) { + $this->outputData('postinstall', 'Error: post-install script ' . + 'modified the variables or prompts, severe security risk. ' . + 'Will instead use the defaults from the package.xml'); + $prompts = $group['param']; + } + } + } + + $answers = $this->confirmDialog($prompts); + } else { + $answers = $this->confirmDialog($group['param']); + } + } + + if ((isset($answers) && $answers) || !isset($group['param'])) { + if (!isset($answers)) { + $answers = array(); + } + + array_unshift($completedPhases, $group['id']); + if (!$script->run($answers, $group['id'])) { + $script->run($completedPhases, '_undoOnError'); + return; + } + } else { + $script->run($completedPhases, '_undoOnError'); + return; + } + } + } + + /** + * Ask for user input, confirm the answers and continue until the user is satisfied + * @param array an array of arrays, format array('name' => 'paramname', 'prompt' => + * 'text to display', 'type' => 'string'[, default => 'default value']) + * @return array + */ + function confirmDialog($params) + { + $answers = $prompts = $types = array(); + foreach ($params as $param) { + $prompts[$param['name']] = $param['prompt']; + $types[$param['name']] = $param['type']; + $answers[$param['name']] = isset($param['default']) ? $param['default'] : ''; + } + + $tried = false; + do { + if ($tried) { + $i = 1; + foreach ($answers as $var => $value) { + if (!strlen($value)) { + echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n"); + } + $i++; + } + } + + $answers = $this->userDialog('', $prompts, $types, $answers); + $tried = true; + } while (is_array($answers) && count(array_filter($answers)) != count($prompts)); + + return $answers; + } + + function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20) + { + if (!is_array($prompts)) { + return array(); + } + + $testprompts = array_keys($prompts); + $result = $defaults; + + reset($prompts); + if (count($prompts) === 1) { + foreach ($prompts as $key => $prompt) { + $type = $types[$key]; + $default = @$defaults[$key]; + print "$prompt "; + if ($default) { + print "[$default] "; + } + print ": "; + + $line = fgets(STDIN, 2048); + $result[$key] = ($default && trim($line) == '') ? $default : trim($line); + } + + return $result; + } + + $first_run = true; + while (true) { + $descLength = max(array_map('strlen', $prompts)); + $descFormat = "%-{$descLength}s"; + $last = count($prompts); + + $i = 0; + foreach ($prompts as $n => $var) { + $res = isset($result[$n]) ? $result[$n] : null; + printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res); + } + print "\n1-$last, 'all', 'abort', or Enter to continue: "; + + $tmp = trim(fgets(STDIN, 1024)); + if (empty($tmp)) { + break; + } + + if ($tmp == 'abort') { + return false; + } + + if (isset($testprompts[(int)$tmp - 1])) { + $var = $testprompts[(int)$tmp - 1]; + $desc = $prompts[$var]; + $current = @$result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if ($tmp !== '') { + $result[$var] = $tmp; + } + } elseif ($tmp == 'all') { + foreach ($prompts as $var => $desc) { + $current = $result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if (trim($tmp) !== '') { + $result[$var] = trim($tmp); + } + } + } + + $first_run = false; + } + + return $result; + } + + function userConfirm($prompt, $default = 'yes') + { + trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR); + static $positives = array('y', 'yes', 'on', '1'); + static $negatives = array('n', 'no', 'off', '0'); + print "$this->lp$prompt [$default] : "; + $fp = fopen("php://stdin", "r"); + $line = fgets($fp, 2048); + fclose($fp); + $answer = strtolower(trim($line)); + if (empty($answer)) { + $answer = $default; + } + if (in_array($answer, $positives)) { + return true; + } + if (in_array($answer, $negatives)) { + return false; + } + if (in_array($default, $positives)) { + return true; + } + return false; + } + + function outputData($data, $command = '_default') + { + switch ($command) { + case 'channel-info': + foreach ($data as $type => $section) { + if ($type == 'main') { + $section['data'] = array_values($section['data']); + } + + $this->outputData($section); + } + break; + case 'install': + case 'upgrade': + case 'upgrade-all': + if (is_array($data) && isset($data['release_warnings'])) { + $this->_displayLine(''); + $this->_startTable(array( + 'border' => false, + 'caption' => 'Release Warnings' + )); + $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55))); + $this->_endTable(); + $this->_displayLine(''); + } + + $this->_displayLine(is_array($data) ? $data['data'] : $data); + break; + case 'search': + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + $packages = array(); + foreach($data['data'] as $category) { + foreach($category as $name => $pkg) { + $packages[$pkg[0]] = $pkg; + } + } + + $p = array_keys($packages); + natcasesort($p); + foreach ($p as $name) { + $this->_tableRow($packages[$name], null, array(1 => array('wrap' => 55))); + } + + $this->_endTable(); + break; + case 'list-all': + if (!isset($data['data'])) { + $this->_displayLine('No packages in channel'); + break; + } + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + $packages = array(); + foreach($data['data'] as $category) { + foreach($category as $name => $pkg) { + $packages[$pkg[0]] = $pkg; + } + } + + $p = array_keys($packages); + natcasesort($p); + foreach ($p as $name) { + $pkg = $packages[$name]; + unset($pkg[4], $pkg[5]); + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + + $this->_endTable(); + break; + case 'config-show': + $data['border'] = false; + $opts = array( + 0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), $opts); + } + + foreach ($data['data'] as $group) { + foreach ($group as $value) { + if ($value[2] == '') { + $value[2] = "<not set>"; + } + + $this->_tableRow($value, null, $opts); + } + } + + $this->_endTable(); + break; + case 'remote-info': + $d = $data; + $data = array( + 'caption' => 'Package details:', + 'border' => false, + 'data' => array( + array("Latest", $data['stable']), + array("Installed", $data['installed']), + array("Package", $data['name']), + array("License", $data['license']), + array("Category", $data['category']), + array("Summary", $data['summary']), + array("Description", $data['description']), + ), + ); + + if (isset($d['deprecated']) && $d['deprecated']) { + $conf = &PEAR_Config::singleton(); + $reg = $conf->getRegistry(); + $name = $reg->parsedPackageNameToString($d['deprecated'], true); + $data['data'][] = array('Deprecated! use', $name); + } + default: { + if (is_array($data)) { + $this->_startTable($data); + $count = count($data['data'][0]); + if ($count == 2) { + $opts = array(0 => array('wrap' => 25), + 1 => array('wrap' => 48) + ); + } elseif ($count == 3) { + $opts = array(0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + } else { + $opts = null; + } + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], + array('bold' => true), + $opts); + } + + if (is_array($data['data'])) { + foreach($data['data'] as $row) { + $this->_tableRow($row, null, $opts); + } + } else { + $this->_tableRow(array($data['data']), null, $opts); + } + $this->_endTable(); + } else { + $this->_displayLine($data); + } + } + } + } + + function log($text, $append_crlf = true) + { + if ($append_crlf) { + return $this->_displayLine($text); + } + + return $this->_display($text); + } + + function bold($text) + { + if (empty($this->term['bold'])) { + return strtoupper($text); + } + + return $this->term['bold'] . $text . $this->term['normal']; + } + + function _displayHeading($title) + { + print $this->lp.$this->bold($title)."\n"; + print $this->lp.str_repeat("=", strlen($title))."\n"; + } + + function _startTable($params = array()) + { + $params['table_data'] = array(); + $params['widest'] = array(); // indexed by column + $params['highest'] = array(); // indexed by row + $params['ncols'] = 0; + $this->params = $params; + } + + function _tableRow($columns, $rowparams = array(), $colparams = array()) + { + $highest = 1; + for ($i = 0; $i < count($columns); $i++) { + $col = &$columns[$i]; + if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) { + $col = wordwrap($col, $colparams[$i]['wrap']); + } + + if (strpos($col, "\n") !== false) { + $multiline = explode("\n", $col); + $w = 0; + foreach ($multiline as $n => $line) { + $len = strlen($line); + if ($len > $w) { + $w = $len; + } + } + $lines = count($multiline); + } else { + $w = strlen($col); + } + + if (isset($this->params['widest'][$i])) { + if ($w > $this->params['widest'][$i]) { + $this->params['widest'][$i] = $w; + } + } else { + $this->params['widest'][$i] = $w; + } + + $tmp = count_chars($columns[$i], 1); + // handle unix, mac and windows formats + $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1; + if ($lines > $highest) { + $highest = $lines; + } + } + + if (count($columns) > $this->params['ncols']) { + $this->params['ncols'] = count($columns); + } + + $new_row = array( + 'data' => $columns, + 'height' => $highest, + 'rowparams' => $rowparams, + 'colparams' => $colparams, + ); + $this->params['table_data'][] = $new_row; + } + + function _endTable() + { + extract($this->params); + if (!empty($caption)) { + $this->_displayHeading($caption); + } + + if (count($table_data) === 0) { + return; + } + + if (!isset($width)) { + $width = $widest; + } else { + for ($i = 0; $i < $ncols; $i++) { + if (!isset($width[$i])) { + $width[$i] = $widest[$i]; + } + } + } + + $border = false; + if (empty($border)) { + $cellstart = ''; + $cellend = ' '; + $rowend = ''; + $padrowend = false; + $borderline = ''; + } else { + $cellstart = '| '; + $cellend = ' '; + $rowend = '|'; + $padrowend = true; + $borderline = '+'; + foreach ($width as $w) { + $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1); + $borderline .= '+'; + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + + for ($i = 0; $i < count($table_data); $i++) { + extract($table_data[$i]); + if (!is_array($rowparams)) { + $rowparams = array(); + } + + if (!is_array($colparams)) { + $colparams = array(); + } + + $rowlines = array(); + if ($height > 1) { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]); + if (count($rowlines[$c]) < $height) { + $rowlines[$c] = array_pad($rowlines[$c], $height, ''); + } + } + } else { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = array($data[$c]); + } + } + + for ($r = 0; $r < $height; $r++) { + $rowtext = ''; + for ($c = 0; $c < count($data); $c++) { + if (isset($colparams[$c])) { + $attribs = array_merge($rowparams, $colparams); + } else { + $attribs = $rowparams; + } + + $w = isset($width[$c]) ? $width[$c] : 0; + //$cell = $data[$c]; + $cell = $rowlines[$c][$r]; + $l = strlen($cell); + if ($l > $w) { + $cell = substr($cell, 0, $w); + } + + if (isset($attribs['bold'])) { + $cell = $this->bold($cell); + } + + if ($l < $w) { + // not using str_pad here because we may + // add bold escape characters to $cell + $cell .= str_repeat(' ', $w - $l); + } + + $rowtext .= $cellstart . $cell . $cellend; + } + + if (!$border) { + $rowtext = rtrim($rowtext); + } + + $rowtext .= $rowend; + $this->_displayLine($rowtext); + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + } + + function _displayLine($text) + { + print "$this->lp$text\n"; + } + + function _display($text) + { + print $text; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer.php b/includes/pear/PEAR/Installer.php new file mode 100644 index 0000000..867b9ed --- /dev/null +++ b/includes/pear/PEAR/Installer.php @@ -0,0 +1,1753 @@ +<?php +/** + * PEAR_Installer + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Martin Jansen <mj@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Used for installation groups in package.xml 2.0 and platform exceptions + */ +require_once 'OS/Guess.php'; +require_once 'PEAR/Downloader.php'; + +define('PEAR_INSTALLER_NOBINARY', -240); +/** + * Administration class used to install PEAR packages and maintain the + * installed package database. + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Martin Jansen <mj@php.net> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Installer extends PEAR_Downloader +{ + /** name of the package directory, for example Foo-1.0 + * @var string + */ + var $pkgdir; + + /** directory where PHP code files go + * @var string + */ + var $phpdir; + + /** directory where PHP extension files go + * @var string + */ + var $extdir; + + /** directory where documentation goes + * @var string + */ + var $docdir; + + /** installation root directory (ala PHP's INSTALL_ROOT or + * automake's DESTDIR + * @var string + */ + var $installroot = ''; + + /** debug level + * @var int + */ + var $debug = 1; + + /** temporary directory + * @var string + */ + var $tmpdir; + + /** + * PEAR_Registry object used by the installer + * @var PEAR_Registry + */ + var $registry; + + /** + * array of PEAR_Downloader_Packages + * @var array + */ + var $_downloadedPackages; + + /** List of file transactions queued for an install/upgrade/uninstall. + * + * Format: + * array( + * 0 => array("rename => array("from-file", "to-file")), + * 1 => array("delete" => array("file-to-delete")), + * ... + * ) + * + * @var array + */ + var $file_operations = array(); + + /** + * PEAR_Installer constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Installer(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + $this->debug = $this->config->get('verbose'); + } + + function setOptions($options) + { + $this->_options = $options; + } + + function setConfig(&$config) + { + $this->config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function _removeBackups($files) + { + foreach ($files as $path) { + $this->addFileOperation('removebackup', array($path)); + } + } + + /** + * Delete a package's installed files, does not remove empty directories. + * + * @param string package name + * @param string channel name + * @param bool if true, then files are backed up first + * @return bool TRUE on success, or a PEAR error on failure + * @access protected + */ + function _deletePackageFiles($package, $channel = false, $backup = false) + { + if (!$channel) { + $channel = 'pear.php.net'; + } + + if (!strlen($package)) { + return $this->raiseError("No package to uninstall given"); + } + + if (strtolower($package) == 'pear' && $channel == 'pear.php.net') { + // to avoid race conditions, include all possible needed files + require_once 'PEAR/Task/Common.php'; + require_once 'PEAR/Task/Replace.php'; + require_once 'PEAR/Task/Unixeol.php'; + require_once 'PEAR/Task/Windowseol.php'; + require_once 'PEAR/PackageFile/v1.php'; + require_once 'PEAR/PackageFile/v2.php'; + require_once 'PEAR/PackageFile/Generator/v1.php'; + require_once 'PEAR/PackageFile/Generator/v2.php'; + } + + $filelist = $this->_registry->packageInfo($package, 'filelist', $channel); + if ($filelist == null) { + return $this->raiseError("$channel/$package not installed"); + } + + $ret = array(); + foreach ($filelist as $file => $props) { + if (empty($props['installed_as'])) { + continue; + } + + $path = $props['installed_as']; + if ($backup) { + $this->addFileOperation('backup', array($path)); + $ret[] = $path; + } + + $this->addFileOperation('delete', array($path)); + } + + if ($backup) { + return $ret; + } + + return true; + } + + /** + * @param string filename + * @param array attributes from <file> tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile($file, $atts, $tmp_path, $options) + { + // return if this file is meant for another platform + static $os; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + if (isset($atts['platform'])) { + if (empty($os)) { + $os = new OS_Guess; + } + + if (strlen($atts['platform']) && $atts['platform']{0} == '!') { + $negate = true; + $platform = substr($atts['platform'], 1); + } else { + $negate = false; + $platform = $atts['platform']; + } + + if ((bool) $os->matchSignature($platform) === $negate) { + $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); + return PEAR_INSTALLER_SKIPPED; + } + } + + $channel = $this->pkginfo->getChannel(); + // assemble the destination paths + switch ($atts['role']) { + case 'src': + case 'extsrc': + $this->source_files++; + return; + case 'doc': + case 'data': + case 'test': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . + DIRECTORY_SEPARATOR . $this->pkginfo->getPackage(); + unset($atts['baseinstalldir']); + break; + case 'ext': + case 'php': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel); + break; + case 'script': + $dest_dir = $this->config->get('bin_dir', null, $channel); + break; + default: + return $this->raiseError("Invalid role `$atts[role]' for file $file"); + } + + $save_destdir = $dest_dir; + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_file, $orig_file)); + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']); + } else { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + + if ( + empty($this->_options['register-only']) && + (!file_exists($dest_dir) || !is_dir($dest_dir)) + ) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (empty($atts['replacements'])) { + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($atts['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { + // file with replacements + if (!file_exists($orig_file)) { + return $this->raiseError("file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($atts['md5sum'])) { + $md5sum = md5($contents); + } + + $subst_from = $subst_to = array(); + foreach ($atts['replacements'] as $a) { + $to = ''; + if ($a['type'] == 'php-const') { + if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) { + eval("\$to = $a[to];"); + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid php-const replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'pear-config') { + if ($a['to'] == 'master_server') { + $chan = $this->_registry->getChannel($channel); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $to = $this->config->get($a['to'], null, $channel); + } + } else { + $to = $this->config->get($a['to'], null, $channel); + } + + if (is_null($to)) { + if (!isset($options['soft'])) { + $this->log(0, "invalid pear-config replacement: $a[to]"); + } + + continue; + } + } elseif ($a['type'] == 'package-info') { + if ($t = $this->pkginfo->packageInfo($a['to'])) { + $to = $t; + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid package-info replacement: $a[to]"); + } + + continue; + } + } + + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + + $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (@fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + } + + // check the md5 + if (isset($md5sum)) { + if (strtolower($md5sum) === strtolower($atts['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } + + // set file permissions + if (!OS_WINDOWS) { + $umask = $this->config->get('umask'); + if ($atts['role'] == 'script') { + $mode = 0777 & ~(int)octdec($umask); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($umask); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + + if ($atts['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, + $atts['role'] == 'ext')); + } + } + + // Store the full path where the file was installed for easy unistall + if ($atts['role'] != 'script') { + $loc = $this->config->get($atts['role'] . '_dir'); + } else { + $loc = $this->config->get('bin_dir'); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("installed_as", array($file, $installed_as, + $loc, + dirname(substr($installedas_dest_file, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string filename + * @param array attributes from <file> tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options) + { + $atts = $real_atts; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channel = $pkg->getChannel(); + // assemble the destination paths + $roles = PEAR_Installer_Role::getValidRoles($pkg->getPackageType()); + if (!in_array($atts['attribs']['role'], $roles)) { + return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . + "' for file $file"); + } + + $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config); + $err = $role->setup($this, $pkg, $atts['attribs'], $file); + if (PEAR::isError($err)) { + return $err; + } + + if (!$role->isInstallable()) { + return; + } + + $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path); + if (PEAR::isError($info)) { + return $info; + } + + list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info; + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + + if (empty($this->_options['register-only'])) { + if (!file_exists($dest_dir) || !is_dir($dest_dir)) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + } + + $attribs = $atts['attribs']; + unset($atts['attribs']); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!count($atts)) { // no tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($attribs['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { // file with tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($attribs['md5sum'])) { + $md5sum = md5($contents); + } + + foreach ($atts as $tag => $raw) { + $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->config, $this, PEAR_TASK_INSTALL); + if (!$task->isScript()) { // scripts are only handled after installation + $task->init($raw, $attribs, $pkg->getLastInstalledVersion()); + $res = $task->startSession($pkg, $contents, $final_dest_file); + if ($res === false) { + continue; // skip this file + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + } + } + + // check the md5 + if (isset($md5sum)) { + // Make sure the original md5 sum matches with expected + if (strtolower($md5sum) === strtolower($attribs['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + + if (isset($contents)) { + // set md5 sum based on $content in case any tasks were run. + $real_atts['attribs']['md5sum'] = md5($contents); + } + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } else { + $real_atts['attribs']['md5sum'] = md5_file($dest_file); + } + + //set file permissions + if (!OS_WINDOWS) { + if ($role->isExecutable()) { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($attribs['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + + if ($attribs['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension())); + } + } + + // Store the full path where the file was installed for easy uninstall + if ($attribs['role'] != 'src') { + $loc = $this->config->get($role->getLocationConfig(), null, $channel); + $this->addFileOperation('installed_as', array($file, $installed_as, + $loc, + dirname(substr($installed_as, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + /** + * Add a file operation to the current file transaction. + * + * @see startFileTransaction() + * @param string $type This can be one of: + * - rename: rename a file ($data has 3 values) + * - backup: backup an existing file ($data has 1 value) + * - removebackup: clean up backups created during install ($data has 1 value) + * - chmod: change permissions on a file ($data has 2 values) + * - delete: delete a file ($data has 1 value) + * - rmdir: delete a directory if empty ($data has 1 value) + * - installed_as: mark a file as installed ($data has 4 values). + * @param array $data For all file operations, this array must contain the + * full path to the file or directory that is being operated on. For + * the rename command, the first parameter must be the file to rename, + * the second its new name, the third whether this is a PHP extension. + * + * The installed_as operation contains 4 elements in this order: + * 1. Filename as listed in the filelist element from package.xml + * 2. Full path to the installed file + * 3. Full path from the php_dir configuration variable used in this + * installation + * 4. Relative path from the php_dir that this file is installed in + */ + function addFileOperation($type, $data) + { + if (!is_array($data)) { + return $this->raiseError('Internal Error: $data in addFileOperation' + . ' must be an array, was ' . gettype($data)); + } + + if ($type == 'chmod') { + $octmode = decoct($data[0]); + $this->log(3, "adding to transaction: $type $octmode $data[1]"); + } else { + $this->log(3, "adding to transaction: $type " . implode(" ", $data)); + } + $this->file_operations[] = array($type, $data); + } + + function startFileTransaction($rollback_in_case = false) + { + if (count($this->file_operations) && $rollback_in_case) { + $this->rollbackFileTransaction(); + } + $this->file_operations = array(); + } + + function commitFileTransaction() + { + //first, check permissions and such manually + $errors = array(); + foreach ($this->file_operations as $key => $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'rename': + if (!file_exists($data[0])) { + $errors[] = "cannot rename file $data[0], doesn't exist"; + } + + // check that dest dir. is writable + if (!is_writable(dirname($data[1]))) { + $errors[] = "permission denied ($type): $data[1]"; + } + break; + case 'chmod': + // check that file is writable + if (!is_writable($data[1])) { + $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]); + } + break; + case 'delete': + if (!file_exists($data[0])) { + $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted"); + } + + // check that directory is writable + if (file_exists($data[0])) { + if (!is_writable(dirname($data[0]))) { + $errors[] = "permission denied ($type): $data[0]"; + } else { + // make sure the file to be deleted can be opened for writing + $fp = false; + if (!is_dir($data[0]) && + (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) { + $errors[] = "permission denied ($type): $data[0]"; + } elseif ($fp) { + fclose($fp); + } + } + + /* Verify we are not deleting a file owned by another package + * This can happen when a file moves from package A to B in + * an upgrade ala http://pear.php.net/17986 + */ + $info = array( + 'package' => strtolower($this->pkginfo->getName()), + 'channel' => strtolower($this->pkginfo->getChannel()), + ); + $result = $this->_registry->checkFileMap($data[0], $info, '1.1'); + if (is_array($result)) { + $res = array_diff($result, $info); + if (!empty($res)) { + $new = $this->_registry->getPackage($result[1], $result[0]); + $this->file_operations[$key] = false; + $pkginfoName = $this->pkginfo->getName(); + $newChannel = $new->getChannel(); + $newPackage = $new->getName(); + $this->log(3, "file $data[0] was scheduled for removal from $pkginfoName but is owned by $newChannel/$newPackage, removal has been cancelled."); + } + } + } + break; + } + } + + $n = count($this->file_operations); + $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName()); + + $m = count($errors); + if ($m > 0) { + foreach ($errors as $error) { + if (!isset($this->_options['soft'])) { + $this->log(1, $error); + } + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + $this->_dirtree = array(); + // really commit the transaction + foreach ($this->file_operations as $i => $tr) { + if (!$tr) { + // support removal of non-existing backups + continue; + } + + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (!file_exists($data[0])) { + $this->file_operations[$i] = false; + break; + } + + if (!@copy($data[0], $data[0] . '.bak')) { + $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . + '.bak ' . $php_errormsg); + return false; + } + $this->log(3, "+ backup $data[0] to $data[0].bak"); + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + $test = file_exists($data[1]) ? @unlink($data[1]) : null; + if (!$test && file_exists($data[1])) { + if ($data[2]) { + $extra = ', this extension must be installed manually. Rename to "' . + basename($data[1]) . '"'; + } else { + $extra = ''; + } + + if (!isset($this->_options['soft'])) { + $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . + $data[0] . $extra); + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + // permissions issues with rename - copy() is far superior + $perms = @fileperms($data[0]); + if (!@copy($data[0], $data[1])) { + $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . + ' ' . $php_errormsg); + return false; + } + + // copy over permissions, otherwise they are lost + @chmod($data[1], $perms); + @unlink($data[0]); + $this->log(3, "+ mv $data[0] $data[1]"); + break; + case 'chmod': + if (!@chmod($data[1], $data[0])) { + $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . + decoct($data[0]) . ' ' . $php_errormsg); + return false; + } + + $octmode = decoct($data[0]); + $this->log(3, "+ chmod $octmode $data[1]"); + break; + case 'delete': + if (file_exists($data[0])) { + if (!@unlink($data[0])) { + $this->log(1, 'Could not delete ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rm $data[0]"); + } + break; + case 'rmdir': + if (file_exists($data[0])) { + do { + $testme = opendir($data[0]); + while (false !== ($entry = readdir($testme))) { + if ($entry == '.' || $entry == '..') { + continue; + } + closedir($testme); + break 2; // this directory is not empty and can't be + // deleted + } + + closedir($testme); + if (!@rmdir($data[0])) { + $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rmdir $data[0]"); + } while (false); + } + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], $data[1]); + if (!isset($this->_dirtree[dirname($data[1])])) { + $this->_dirtree[dirname($data[1])] = true; + $this->pkginfo->setDirtree(dirname($data[1])); + + while(!empty($data[3]) && dirname($data[3]) != $data[3] && + $data[3] != '/' && $data[3] != '\\') { + $this->pkginfo->setDirtree($pp = + $this->_prependPath($data[3], $data[2])); + $this->_dirtree[$pp] = true; + $data[3] = dirname($data[3]); + } + } + break; + } + } + + $this->log(2, "successfully committed $n file operations"); + $this->file_operations = array(); + return true; + } + + function rollbackFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "rolling back $n file operations"); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (file_exists($data[0] . '.bak')) { + if (file_exists($data[0] && is_writable($data[0]))) { + unlink($data[0]); + } + @copy($data[0] . '.bak', $data[0]); + $this->log(3, "+ restore $data[0] from $data[0].bak"); + } + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + @unlink($data[0]); + $this->log(3, "+ rm $data[0]"); + break; + case 'mkdir': + @rmdir($data[0]); + $this->log(3, "+ rmdir $data[0]"); + break; + case 'chmod': + break; + case 'delete': + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], false); + break; + } + } + $this->pkginfo->resetDirtree(); + $this->file_operations = array(); + } + + function mkDirHier($dir) + { + $this->addFileOperation('mkdir', array($dir)); + return parent::mkDirHier($dir); + } + + /** + * Download any files and their dependencies, if necessary + * + * @param array a mixed list of package names, local files, or package.xml + * @param PEAR_Config + * @param array options from the command line + * @param array this is the array that will be populated with packages to + * install. Format of each entry: + * + * <code> + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * </code> + * @param array this will be populated with any error messages + * @param false private recursion variable + * @param false private recursion variable + * @param false private recursion variable + * @deprecated in favor of PEAR_Downloader + */ + function download($packages, $options, &$config, &$installpackages, + &$errors, $installed = false, $willinstall = false, $state = false) + { + // trickiness: initialize here + parent::PEAR_Downloader($this->ui, $options, $config); + $ret = parent::download($packages); + $errors = $this->getErrorMsgs(); + $installpackages = $this->getDownloadedPackages(); + trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . + "in favor of PEAR_Downloader class", E_USER_WARNING); + return $ret; + } + + function _parsePackageXml(&$descfile) + { + // Parse xml file + $pkg = new PEAR_PackageFile($this->config, $this->debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($p)) { + if (is_array($p->getUserInfo())) { + foreach ($p->getUserInfo() as $err) { + $loglevel = $err['level'] == 'error' ? 0 : 1; + if (!isset($this->_options['soft'])) { + $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']); + } + } + } + return $this->raiseError('Installation failed: invalid package file'); + } + + $descfile = $p->getPackageFile(); + return $p; + } + + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setDownloadedPackages(&$pkgs) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($pkgs); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return $err; + } + $this->_downloadedPackages = &$pkgs; + } + + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setUninstallPackages(&$pkgs) + { + $this->_downloadedPackages = &$pkgs; + } + + function getInstallPackages() + { + return $this->_downloadedPackages; + } + + /** + * Installs the files within the package file specified. + * + * @param string|PEAR_Downloader_Package $pkgfile path to the package file, + * or a pre-initialized packagefile object + * @param array $options + * recognized options: + * - installroot : optional prefix directory for installation + * - force : force installation + * - register-only : update registry but don't install files + * - upgrade : upgrade existing install + * - soft : fail silently + * - nodeps : ignore dependency conflicts/missing dependencies + * - alldeps : install all dependencies + * - onlyreqdeps : install only required dependencies + * + * @return array|PEAR_Error package info if successful + */ + function install($pkgfile, $options = array()) + { + $this->_options = $options; + $this->_registry = &$this->config->getRegistry(); + if (is_object($pkgfile)) { + $dlpkg = &$pkgfile; + $pkg = $pkgfile->getPackageFile(); + $pkgfile = $pkg->getArchiveFile(); + $descfile = $pkg->getPackageFile(); + } else { + $descfile = $pkgfile; + $pkg = $this->_parsePackageXml($descfile); + if (PEAR::isError($pkg)) { + return $pkg; + } + } + + $tmpdir = dirname($descfile); + if (realpath($descfile) != realpath($pkgfile)) { + // Use the temp_dir since $descfile can contain the download dir path + $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net'); + $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"'); + + $tar = new Archive_Tar($pkgfile); + if (!$tar->extract($tmpdir)) { + return $this->raiseError("unable to unpack $pkgfile"); + } + } + + $name = $pkg->getName(); + $channel = $pkg->getChannel(); + if (isset($this->_options['packagingroot'])) { + $regdir = $this->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $this->_options['packagingroot']); + + $packrootphp_dir = $this->_prependPath( + $this->config->get('php_dir', null, $channel), + $this->_options['packagingroot']); + } + + if (isset($options['installroot'])) { + $this->config->setInstallRoot($options['installroot']); + $this->_registry = &$this->config->getRegistry(); + $installregistry = &$this->_registry; + $this->installroot = ''; // all done automagically now + $php_dir = $this->config->get('php_dir', null, $channel); + } else { + $this->config->setInstallRoot(false); + $this->_registry = &$this->config->getRegistry(); + if (isset($this->_options['packagingroot'])) { + $installregistry = &new PEAR_Registry($regdir); + if (!$installregistry->channelExists($channel, true)) { + // we need to fake a channel-discover of this channel + $chanobj = $this->_registry->getChannel($channel, true); + $installregistry->addChannel($chanobj); + } + $php_dir = $packrootphp_dir; + } else { + $installregistry = &$this->_registry; + $php_dir = $this->config->get('php_dir', null, $channel); + } + $this->installroot = ''; + } + + // checks to do when not in "force" mode + if (empty($options['force']) && + (file_exists($this->config->get('php_dir')) && + is_dir($this->config->get('php_dir')))) { + $testp = $channel == 'pear.php.net' ? $name : array($channel, $name); + $instfilelist = $pkg->getInstallationFileList(true); + if (PEAR::isError($instfilelist)) { + return $instfilelist; + } + + // ensure we have the most accurate registry + $installregistry->flushFileMap(); + $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1'); + if (PEAR::isError($test)) { + return $test; + } + + if (sizeof($test)) { + $pkgs = $this->getInstallPackages(); + $found = false; + foreach ($pkgs as $param) { + if ($pkg->isSubpackageOf($param)) { + $found = true; + break; + } + } + + if ($found) { + // subpackages can conflict with earlier versions of parent packages + $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel()); + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_array($info)) { + if (strtolower($info[1]) == strtolower($param->getPackage()) && + strtolower($info[0]) == strtolower($param->getChannel()) + ) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } else { + if (strtolower($param->getChannel()) != 'pear.php.net') { + continue; + } + + if (strtolower($info) == strtolower($param->getPackage())) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } + } + + $pfk = &new PEAR_PackageFile($this->config); + $parentpkg = &$pfk->fromArray($parentreg); + $installregistry->updatePackage2($parentpkg); + } + + if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) { + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_string($info)) { + // pear.php.net packages are always stored as strings + if (strtolower($info) == strtolower($param->getPackage())) { + // upgrading existing package + unset($test[$file]); + } + } + } + } + + if (count($test)) { + $msg = "$channel/$name: conflicting files found:\n"; + $longest = max(array_map("strlen", array_keys($test))); + $fmt = "%${longest}s (%s)\n"; + foreach ($test as $file => $info) { + if (!is_array($info)) { + $info = array('pear.php.net', $info); + } + $info = $info[0] . '/' . $info[1]; + $msg .= sprintf($fmt, $file, $info); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError($msg); + } + + if (!isset($options['soft'])) { + $this->log(0, "WARNING: $msg"); + } + } + } + } + + $this->startFileTransaction(); + + // Figure out what channel to use and if the package exists or not + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($name, $channel); + if (!$test) { + $test = $installregistry->packageExists($name, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($name, $channel); + } + + // checks to do only when installing new packages + if (empty($options['upgrade']) && empty($options['soft'])) { + if (empty($options['force']) && $test) { + return $this->raiseError("$channel/$name is already installed"); + } + } else { + // Upgrade + if ($test) { + $v1 = $installregistry->packageInfo($name, 'version', $usechannel); + $v2 = $pkg->getVersion(); + $cmp = version_compare("$v1", "$v2", 'gt'); + if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) { + return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); + } + } + } + + // Do cleanups for upgrade and install, remove old release's files first + if ($test && empty($options['register-only'])) { + $err = $this->_deletePackageFiles($name, $usechannel, true); + if (PEAR::isError($err)) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + $backedup = $err; + } + } + + // Copy files to dest dir + + // info from the package it self we want to access from _installFile + $this->pkginfo = &$pkg; + // used to determine whether we should build any C code + $this->source_files = 0; + + $savechannel = $this->config->get('default_channel'); + if (empty($options['register-only']) && !is_dir($php_dir)) { + if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) { + return $this->raiseError("no installation destination directory '$php_dir'\n"); + } + } + + if (substr($pkgfile, -4) != '.xml') { + $tmpdir .= DIRECTORY_SEPARATOR . $name . '-' . $pkg->getVersion(); + } + + $this->configSet('default_channel', $channel); + // install files + + $filelist = $pkg->getInstallationFilelist(); + if (PEAR::isError($filelist)) { + return $filelist; + } + + $p = &$installregistry->getPackage($name, $channel); + $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false; + + $pkg->resetFilelist(); + $version = $installregistry->packageInfo($pkg->getPackage(), 'version', $pkg->getChannel()); + $pkg->setLastInstalledVersion($version); + foreach ($filelist as $file => $atts) { + $this->expectError(PEAR_INSTALLER_FAILED); + if ($pkg->getPackagexmlVersion() == '1.0') { + $res = $this->_installFile($file, $atts, $tmpdir, $options); + } else { + $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options); + } + $this->popExpect(); + + if (PEAR::isError($res)) { + if (empty($options['ignore-errors'])) { + $this->rollbackFileTransaction(); + if ($res->getMessage() == "file does not exist") { + $this->raiseError("file $file in package.xml does not exist"); + } + + return $this->raiseError($res); + } + + if (!isset($options['soft'])) { + $this->log(0, "Warning: " . $res->getMessage()); + } + } + + $real = isset($atts['attribs']) ? $atts['attribs'] : $atts; + if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') { + // Register files that were installed + $pkg->installedFile($file, $atts); + } + } + + // compile and install source files + if ($this->source_files > 0 && empty($options['nobuild'])) { + if (!isset($options['__compile_configureoptions'])) { + $options['__compile_configureoptions'] = array(); + } + + if (PEAR::isError($err = + $this->_compileSourceFiles($savechannel, $pkg, $options['__compile_configureoptions']))) { + return $err; + } + } + + if (isset($backedup)) { + $this->_removeBackups($backedup); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED); + } + + // See if package already exists + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($name, $channel); + if (!$test) { + $test = $installregistry->packageExists($name, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($name, $channel); + } + + $ret = false; + $installphase = 'install'; + $oldversion = false; + // Register that the package is installed + if (empty($options['upgrade'])) { + // if 'force' is used, replace the info in registry + if (!empty($options['force']) && $test) { + $oldversion = $installregistry->packageInfo($name, 'version', $usechannel); + $installregistry->deletePackage($name, $usechannel); + } + $ret = $installregistry->addPackage2($pkg); + } else { + if ($dirtree) { + $this->startFileTransaction(); + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + $this->commitFileTransaction(); + } + + // new: upgrade installs a package if it isn't installed + if (!$test) { + $ret = $installregistry->addPackage2($pkg); + } else { + if ($usechannel != $channel) { + $installregistry->deletePackage($name, $usechannel); + $ret = $installregistry->addPackage2($pkg); + } else { + $ret = $installregistry->updatePackage2($pkg); + } + $installphase = 'upgrade'; + } + } + + if (!$ret) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError("Adding package $channel/$name to registry failed"); + } + // }}} + + $this->configSet('default_channel', $savechannel); + if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist + if (PEAR_Task_Common::hasPostinstallTasks()) { + PEAR_Task_Common::runPostinstallTasks($installphase); + } + } + + return $pkg->toArray(true); + } + + /** + * @param string + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function _compileSourceFiles($savechannel, &$filelist, $configure_options = array()) + { + require_once 'PEAR/Builder.php'; + $this->log(1, "$this->source_files source files, building"); + $bob = &new PEAR_Builder($this->ui); + $bob->debug = $this->debug; + $built = $bob->build($filelist, array(&$this, '_buildCallback'), $configure_options); + if (PEAR::isError($built)) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $built; + } + + $this->log(1, "\nBuild process completed successfully"); + foreach ($built as $ext) { + $bn = basename($ext['file']); + list($_ext_name, $_ext_suff) = explode('.', $bn); + if ($_ext_suff == '.so' || $_ext_suff == '.dll') { + if (extension_loaded($_ext_name)) { + $this->raiseError("Extension '$_ext_name' already loaded. " . + 'Please unload it in your php.ini file ' . + 'prior to install or upgrade'); + } + $role = 'ext'; + } else { + $role = 'src'; + } + + $dest = $ext['dest']; + $packagingroot = ''; + if (isset($this->_options['packagingroot'])) { + $packagingroot = $this->_options['packagingroot']; + } + + $copyto = $this->_prependPath($dest, $packagingroot); + $extra = $copyto != $dest ? " as '$copyto'" : ''; + $this->log(1, "Installing '$dest'$extra"); + + $copydir = dirname($copyto); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!file_exists($copydir) || !is_dir($copydir)) { + if (!$this->mkDirHier($copydir)) { + return $this->raiseError("failed to mkdir $copydir", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ mkdir $copydir"); + } + + if (!@copy($ext['file'], $copyto)) { + return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $ext[file] $copyto"); + $this->addFileOperation('rename', array($ext['file'], $copyto)); + if (!OS_WINDOWS) { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + $this->addFileOperation('chmod', array($mode, $copyto)); + if (!@chmod($copyto, $mode)) { + $this->log(0, "failed to change mode of $copyto ($php_errormsg)"); + } + } + } + + $data = array( + 'role' => $role, + 'name' => $bn, + 'installed_as' => $dest, + 'php_api' => $ext['php_api'], + 'zend_mod_api' => $ext['zend_mod_api'], + 'zend_ext_api' => $ext['zend_ext_api'], + ); + + if ($filelist->getPackageXmlVersion() == '1.0') { + $filelist->installedFile($bn, $data); + } else { + $filelist->installedFile($bn, array('attribs' => $data)); + } + } + } + + function &getUninstallPackages() + { + return $this->_downloadedPackages; + } + + /** + * Uninstall a package + * + * This method removes all files installed by the application, and then + * removes any empty directories. + * @param string package name + * @param array Command-line options. Possibilities include: + * + * - installroot: base installation dir, if not the default + * - register-only : update registry but don't remove files + * - nodeps: do not process dependencies of other packages to ensure + * uninstallation does not break things + */ + function uninstall($package, $options = array()) + { + $installRoot = isset($options['installroot']) ? $options['installroot'] : ''; + $this->config->setInstallRoot($installRoot); + + $this->installroot = ''; + $this->_registry = &$this->config->getRegistry(); + if (is_object($package)) { + $channel = $package->getChannel(); + $pkg = $package; + $package = $pkg->getPackage(); + } else { + $pkg = false; + $info = $this->_registry->parsePackageName($package, + $this->config->get('default_channel')); + $channel = $info['channel']; + $package = $info['package']; + } + + $savechannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $channel); + if (!is_object($pkg)) { + $pkg = $this->_registry->getPackage($package, $channel); + } + + if (!$pkg) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError($this->_registry->parsedPackageNameToString( + array( + 'channel' => $channel, + 'package' => $package + ), true) . ' not installed'); + } + + if ($pkg->getInstalledBinary()) { + // this is just an alias for a binary package + return $this->_registry->deletePackage($package, $channel); + } + + $filelist = $pkg->getFilelist(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $depchecker = &new PEAR_Dependency2($this->config, $options, + array('channel' => $channel, 'package' => $package), + PEAR_VALIDATE_UNINSTALLING); + $e = $depchecker->validatePackageUninstall($this); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($e); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $e->getMessage()); + } + } elseif (is_array($e)) { + if (!isset($options['soft'])) { + $this->log(0, $e[0]); + } + } + + $this->pkginfo = &$pkg; + // pretty much nothing happens if we are only registering the uninstall + if (empty($options['register-only'])) { + // Delete the files + $this->startFileTransaction(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) { + PEAR::popErrorHandling(); + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + PEAR::popErrorHandling(); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } else { + $this->startFileTransaction(); + $dirtree = $pkg->getDirTree(); + if ($dirtree === false) { + $this->configSet('default_channel', $savechannel); + return $this->_registry->deletePackage($package, $channel); + } + + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } + } + } + + $this->configSet('default_channel', $savechannel); + // Register that the package is no longer installed + return $this->_registry->deletePackage($package, $channel); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * It also removes duplicate dependencies + * @param array an array of PEAR_PackageFile_v[1/2] objects + * @return array|PEAR_Error array of array(packagefilename, package.xml contents) + */ + function sortPackagesForUninstall(&$packages) + { + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config); + if (PEAR::isError($this->_dependencyDB)) { + return $this->_dependencyDB; + } + usort($packages, array(&$this, '_sortUninstall')); + } + + function _sortUninstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return -1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return 1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependencyDB->dependsOn($a, $b)) { + return -1; + } + if ($this->_dependencyDB->dependsOn($b, $a)) { + return 1; + } + return 0; + } + + function _sortDirs($a, $b) + { + if (strnatcmp($a, $b) == -1) return 1; + if (strnatcmp($a, $b) == 1) return -1; + return 0; + } + + function _buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } +} diff --git a/includes/pear/PEAR/Installer/Role.php b/includes/pear/PEAR/Installer/Role.php new file mode 100644 index 0000000..2eba3da --- /dev/null +++ b/includes/pear/PEAR/Installer/Role.php @@ -0,0 +1,267 @@ +<?php +/** + * PEAR_Installer_Role + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class for installer roles + */ +require_once 'PEAR/Installer/Role/Common.php'; +require_once 'PEAR/XMLParser.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role +{ + /** + * Set up any additional configuration variables that file roles require + * + * Never call this directly, it is called by the PEAR_Config constructor + * @param PEAR_Config + */ + public static function initializeConfig(&$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $class => $info) { + if (!$info['config_vars']) { + continue; + } + + $config->_addConfigVars($class, $info['config_vars']); + } + } + + /** + * @param PEAR_PackageFile_v2 + * @param string role name + * @param PEAR_Config + * @return PEAR_Installer_Role_Common + */ + public static function &factory($pkg, $role, &$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + if (!in_array($role, PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + $a = false; + return $a; + } + + $a = 'PEAR_Installer_Role_' . ucfirst($role); + if (!class_exists($a)) { + require_once str_replace('_', '/', $a) . '.php'; + } + + $b = new $a($config); + return $b; + } + + /** + * Get a list of file roles that are valid for the particular release type. + * + * For instance, src files serve no purpose in regular php releases. + * @param string + * @param bool clear cache + * @return array + */ + public static function getValidRoles($release, $clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret = array(); + if ($clear) { + $ret = array(); + } + + if (isset($ret[$release])) { + return $ret[$release]; + } + + $ret[$release] = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if (in_array($release, $okreleases['releasetypes'])) { + $ret[$release][] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret[$release]; + } + + /** + * Get a list of roles that require their files to be installed + * + * Most roles must be installed, but src and package roles, for instance + * are pseudo-roles. src files are compiled into a new extension. Package + * roles are actually fully bundled releases of a package + * @param bool clear cache + * @return array + */ + public static function getInstallableRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['installable']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of roles that are affected by the baseinstalldir attribute + * + * Most roles ignore this attribute, and instead install directly into: + * PackageName/filepath + * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php + * @param bool clear cache + * @return array + */ + public static function getBaseinstallRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['honorsbaseinstall']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of file roles that should be analyzed for PHP content at package time, + * like the "php" role. + * @param bool clear cache + * @return array + */ + public static function getPhpRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['phpfile']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * @param string which directory to look for classes, defaults to + * the Installer/Roles subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + */ + public static function registerRoles($dir = null) + { + $GLOBALS['_PEAR_INSTALLER_ROLES'] = array(); + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Role'; + } + + if (!file_exists($dir) || !is_dir($dir)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: does not exist/is not directory"); + } + + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: $php_errmsg"); + } + + while ($entry = readdir($dp)) { + if ($entry{0} == '.' || substr($entry, -4) != '.xml') { + continue; + } + + $class = "PEAR_Installer_Role_".substr($entry, 0, -4); + // List of roles + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'][$class])) { + $file = "$dir/$entry"; + $parser->parse(file_get_contents($file)); + $data = $parser->getData(); + if (!is_array($data['releasetypes'])) { + $data['releasetypes'] = array($data['releasetypes']); + } + + $GLOBALS['_PEAR_INSTALLER_ROLES'][$class] = $data; + } + } + + closedir($dp); + ksort($GLOBALS['_PEAR_INSTALLER_ROLES']); + PEAR_Installer_Role::getBaseinstallRoles(true); + PEAR_Installer_Role::getInstallableRoles(true); + PEAR_Installer_Role::getPhpRoles(true); + PEAR_Installer_Role::getValidRoles('****', true); + return true; + } +} diff --git a/includes/pear/PEAR/Installer/Role/Cfg.php b/includes/pear/PEAR/Installer/Role/Cfg.php new file mode 100644 index 0000000..23af39c --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Cfg.php @@ -0,0 +1,106 @@ +<?php +/** + * PEAR_Installer_Role_Cfg + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Cfg extends PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Installer + */ + var $installer; + + /** + * the md5 of the original file + * + * @var unknown_type + */ + var $md5 = null; + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + $this->installer = &$installer; + $reg = &$this->installer->config->getRegistry(); + $package = $reg->getPackage($pkg->getPackage(), $pkg->getChannel()); + if ($package) { + $filelist = $package->getFilelist(); + if (isset($filelist[$file]) && isset($filelist[$file]['md5sum'])) { + $this->md5 = $filelist[$file]['md5sum']; + } + } + } + + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $test = parent::processInstallation($pkg, $atts, $file, $tmp_path, $layer); + if (@file_exists($test[2]) && @file_exists($test[3])) { + $md5 = md5_file($test[2]); + // configuration has already been installed, check for mods + if ($md5 !== $this->md5 && $md5 !== md5_file($test[3])) { + // configuration has been modified, so save our version as + // configfile-version + $old = $test[2]; + $test[2] .= '.new-' . $pkg->getVersion(); + // backup original and re-install it + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $tmpcfg = $this->config->get('temp_dir'); + $newloc = System::mkdir(array('-p', $tmpcfg)); + if (!$newloc) { + // try temp_dir + $newloc = System::mktemp(array('-d')); + if (!$newloc || PEAR::isError($newloc)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + } else { + $newloc = $tmpcfg; + } + + $temp_file = $newloc . DIRECTORY_SEPARATOR . uniqid('savefile'); + if (!@copy($old, $temp_file)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + + PEAR::popErrorHandling(); + $this->installer->log(0, "WARNING: configuration file $old is being installed as $test[2], you should manually merge in changes to the existing configuration file"); + $this->installer->addFileOperation('rename', array($temp_file, $old, false)); + $this->installer->addFileOperation('delete', array($temp_file)); + } + } + + return $test; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Cfg.xml b/includes/pear/PEAR/Installer/Role/Cfg.xml new file mode 100644 index 0000000..7a415dc --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Cfg.xml @@ -0,0 +1,15 @@ +<role version="1.0"> + <releasetypes>php</releasetypes> + <releasetypes>extsrc</releasetypes> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>cfg_dir</locationconfig> + <honorsbaseinstall /> + <unusualbaseinstall>1</unusualbaseinstall> + <phpfile /> + <executable /> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Common.php b/includes/pear/PEAR/Installer/Role/Common.php new file mode 100644 index 0000000..b2b2a61 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Common.php @@ -0,0 +1,189 @@ +<?php +/** + * Base class for all installation roles. + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class for all installation roles. + * + * This class allows extensibility of file roles. Packages with complex + * customization can now provide custom file roles along with the possibility of + * adding configuration values to match. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Config + * @access protected + */ + var $config; + + /** + * @param PEAR_Config + */ + function PEAR_Installer_Role_Common(&$config) + { + $this->config = $config; + } + + /** + * Retrieve configuration information about a file role from its XML info + * + * @param string $role Role Classname, as in "PEAR_Installer_Role_Data" + * @return array + */ + function getInfo($role) + { + if (empty($GLOBALS['_PEAR_INSTALLER_ROLES'][$role])) { + return PEAR::raiseError('Unknown Role class: "' . $role . '"'); + } + return $GLOBALS['_PEAR_INSTALLER_ROLES'][$role]; + } + + /** + * This is called for each file to set up the directories and files + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array attributes from the <file> tag + * @param string file name + * @return array an array consisting of: + * + * 1 the original, pre-baseinstalldir installation directory + * 2 the final installation directory + * 3 the full path to the final location of the file + * 4 the location of the pre-installation file + */ + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $role = $this->getRoleFromClass(); + $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role); + if (PEAR::isError($info)) { + return $info; + } + + if (!$info['locationconfig']) { + return false; + } + + if ($info['honorsbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($info['locationconfig'], $layer, + $pkg->getChannel()); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } elseif ($info['unusualbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($info['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } else { + $save_destdir = $dest_dir = $this->config->get($info['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + } + + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + + list($dest_dir, $dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_dir, $dest_file, $orig_file)); + + return array($save_destdir, $dest_dir, $dest_file, $orig_file); + } + + /** + * Get the name of the configuration variable that specifies the location of this file + * @return string|false + */ + function getLocationConfig() + { + $role = ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this)))); + $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role); + if (PEAR::isError($info)) { + return $info; + } + + return $info['locationconfig']; + } + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + } + + function isExecutable() + { + $role = $this->getRoleFromClass(); + $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role); + if (PEAR::isError($info)) { + return $info; + } + + return $info['executable']; + } + + function isInstallable() + { + $role = $this->getRoleFromClass(); + $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role); + if (PEAR::isError($info)) { + return $info; + } + + return $info['installable']; + } + + function isExtension() + { + $role = $this->getRoleFromClass(); + $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role); + if (PEAR::isError($info)) { + return $info; + } + + return $info['phpextension']; + } + + function getRoleFromClass() + { + $lower = strtolower(get_class($this)); + return ucfirst(str_replace('pear_installer_role_', '', $lower)); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Data.php b/includes/pear/PEAR/Installer/Role/Data.php new file mode 100644 index 0000000..27d05d4 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Data.php @@ -0,0 +1,28 @@ +<?php +/** + * PEAR_Installer_Role_Data + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Data extends PEAR_Installer_Role_Common {} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Data.xml b/includes/pear/PEAR/Installer/Role/Data.xml new file mode 100644 index 0000000..eae6372 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Data.xml @@ -0,0 +1,15 @@ +<role version="1.0"> + <releasetypes>php</releasetypes> + <releasetypes>extsrc</releasetypes> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>data_dir</locationconfig> + <honorsbaseinstall /> + <unusualbaseinstall /> + <phpfile /> + <executable /> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Doc.php b/includes/pear/PEAR/Installer/Role/Doc.php new file mode 100644 index 0000000..5194e6e --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Doc.php @@ -0,0 +1,28 @@ +<?php +/** + * PEAR_Installer_Role_Doc + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Doc extends PEAR_Installer_Role_Common {} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Doc.xml b/includes/pear/PEAR/Installer/Role/Doc.xml new file mode 100644 index 0000000..173afba --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Doc.xml @@ -0,0 +1,15 @@ +<role version="1.0"> + <releasetypes>php</releasetypes> + <releasetypes>extsrc</releasetypes> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>doc_dir</locationconfig> + <honorsbaseinstall /> + <unusualbaseinstall /> + <phpfile /> + <executable /> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Ext.php b/includes/pear/PEAR/Installer/Role/Ext.php new file mode 100644 index 0000000..ad8988c --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Ext.php @@ -0,0 +1,28 @@ +<?php +/** + * PEAR_Installer_Role_Ext + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Ext extends PEAR_Installer_Role_Common {} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Ext.xml b/includes/pear/PEAR/Installer/Role/Ext.xml new file mode 100644 index 0000000..e2940fe --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Ext.xml @@ -0,0 +1,12 @@ +<role version="1.0"> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>ext_dir</locationconfig> + <honorsbaseinstall>1</honorsbaseinstall> + <unusualbaseinstall /> + <phpfile /> + <executable /> + <phpextension>1</phpextension> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Php.php b/includes/pear/PEAR/Installer/Role/Php.php new file mode 100644 index 0000000..ee7f935 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Php.php @@ -0,0 +1,28 @@ +<?php +/** + * PEAR_Installer_Role_Php + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Php extends PEAR_Installer_Role_Common {} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Php.xml b/includes/pear/PEAR/Installer/Role/Php.xml new file mode 100644 index 0000000..6b9a0e6 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Php.xml @@ -0,0 +1,15 @@ +<role version="1.0"> + <releasetypes>php</releasetypes> + <releasetypes>extsrc</releasetypes> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>php_dir</locationconfig> + <honorsbaseinstall>1</honorsbaseinstall> + <unusualbaseinstall /> + <phpfile>1</phpfile> + <executable /> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Script.php b/includes/pear/PEAR/Installer/Role/Script.php new file mode 100644 index 0000000..8259305 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Script.php @@ -0,0 +1,28 @@ +<?php +/** + * PEAR_Installer_Role_Script + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Script extends PEAR_Installer_Role_Common {} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Script.xml b/includes/pear/PEAR/Installer/Role/Script.xml new file mode 100644 index 0000000..e732cf2 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Script.xml @@ -0,0 +1,15 @@ +<role version="1.0"> + <releasetypes>php</releasetypes> + <releasetypes>extsrc</releasetypes> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>bin_dir</locationconfig> + <honorsbaseinstall>1</honorsbaseinstall> + <unusualbaseinstall /> + <phpfile /> + <executable>1</executable> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Src.php b/includes/pear/PEAR/Installer/Role/Src.php new file mode 100644 index 0000000..3d114d4 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Src.php @@ -0,0 +1,34 @@ +<?php +/** + * PEAR_Installer_Role_Src + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Src extends PEAR_Installer_Role_Common +{ + function setup(&$installer, $pkg, $atts, $file) + { + $installer->source_files++; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Src.xml b/includes/pear/PEAR/Installer/Role/Src.xml new file mode 100644 index 0000000..1034834 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Src.xml @@ -0,0 +1,12 @@ +<role version="1.0"> + <releasetypes>extsrc</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <installable>1</installable> + <locationconfig>temp_dir</locationconfig> + <honorsbaseinstall /> + <unusualbaseinstall /> + <phpfile /> + <executable /> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Test.php b/includes/pear/PEAR/Installer/Role/Test.php new file mode 100644 index 0000000..06747f7 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Test.php @@ -0,0 +1,28 @@ +<?php +/** + * PEAR_Installer_Role_Test + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Test extends PEAR_Installer_Role_Common {} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Test.xml b/includes/pear/PEAR/Installer/Role/Test.xml new file mode 100644 index 0000000..51d5b89 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Test.xml @@ -0,0 +1,15 @@ +<role version="1.0"> + <releasetypes>php</releasetypes> + <releasetypes>extsrc</releasetypes> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>test_dir</locationconfig> + <honorsbaseinstall /> + <unusualbaseinstall /> + <phpfile /> + <executable /> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Www.php b/includes/pear/PEAR/Installer/Role/Www.php new file mode 100644 index 0000000..c463c8b --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Www.php @@ -0,0 +1,28 @@ +<?php +/** + * PEAR_Installer_Role_Www + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Www extends PEAR_Installer_Role_Common {} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Installer/Role/Www.xml b/includes/pear/PEAR/Installer/Role/Www.xml new file mode 100644 index 0000000..7598be3 --- /dev/null +++ b/includes/pear/PEAR/Installer/Role/Www.xml @@ -0,0 +1,15 @@ +<role version="1.0"> + <releasetypes>php</releasetypes> + <releasetypes>extsrc</releasetypes> + <releasetypes>extbin</releasetypes> + <releasetypes>zendextsrc</releasetypes> + <releasetypes>zendextbin</releasetypes> + <installable>1</installable> + <locationconfig>www_dir</locationconfig> + <honorsbaseinstall>1</honorsbaseinstall> + <unusualbaseinstall /> + <phpfile /> + <executable /> + <phpextension /> + <config_vars /> +</role>
\ No newline at end of file diff --git a/includes/pear/PEAR/PackageFile.php b/includes/pear/PEAR/PackageFile.php new file mode 100644 index 0000000..2a29145 --- /dev/null +++ b/includes/pear/PEAR/PackageFile.php @@ -0,0 +1,503 @@ +<?php +/** + * PEAR_PackageFile, package.xml parsing utility class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +/** + * Error code if the package.xml <package> tag does not contain a valid version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1); +/** + * Error code if the package.xml <package> tag version is not supported (version 1.0 and 1.1 are the only supported versions, + * currently + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2); +/** + * Abstraction for the package.xml package description file + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile +{ + /** + * @var PEAR_Config + */ + var $_config; + var $_debug; + + var $_logger = false; + /** + * @var boolean + */ + var $_rawReturn = false; + + /** + * helper for extracting Archive_Tar errors + * @var array + * @access private + */ + var $_extractErrors = array(); + + /** + * + * @param PEAR_Config $config + * @param ? $debug + * @param string @tmpdir Optional temporary directory for uncompressing + * files + */ + function PEAR_PackageFile(&$config, $debug = false) + { + $this->_config = $config; + $this->_debug = $debug; + } + + /** + * Turn off validation - return a parsed package.xml without checking it + * + * This is used by the package-validate command + */ + function rawReturn() + { + $this->_rawReturn = true; + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * Create a PEAR_PackageFile_Parser_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1 + */ + function &parserFactory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/Parser/v' . $version{0} . '.php'; + $version = $version{0}; + $class = "PEAR_PackageFile_Parser_v$version"; + $a = new $class; + return $a; + } + + /** + * For simpler unit-testing + * @return string + */ + function getClassPrefix() + { + return 'PEAR_PackageFile_v'; + } + + /** + * Create a PEAR_PackageFile_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|false + */ + function &factory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/v' . $version{0} . '.php'; + $version = $version{0}; + $class = $this->getClassPrefix() . $version; + $a = new $class; + return $a; + } + + /** + * Create a PEAR_PackageFile_v* from its toArray() method + * + * WARNING: no validation is performed, the array is assumed to be valid, + * always parse from xml if you want validation. + * @param array $arr + * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2|PEAR_Error + * @uses factory() to construct the returned object. + */ + function &fromArray($arr) + { + if (isset($arr['xsdversion'])) { + $obj = &$this->factory($arr['xsdversion']); + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + // The extensive tests are necessary due to changes in PHP 5.4. + if (is_array($arr) + && array_key_exists('package', $arr) + && is_array($arr['package']) + && array_key_exists('attribs', $arr['package']) + && is_array($arr['package']['attribs']) + && array_key_exists('version', $arr['package']['attribs']) + && !empty($arr['package']['attribs']['version'])) + { + $obj = &$this->factory($arr['package']['attribs']['version']); + } else { + $obj = &$this->factory('1.0'); + } + if (!$obj) { + return PEAR::raiseError('Invalid package version.'); + } + + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + /** + * Create a PEAR_PackageFile_v* from an XML string. + * @access public + * @param string $data contents of package.xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string $file full path to the package.xml file (and the files + * it references) + * @param string $archive optional name of the archive that the XML was + * extracted from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses parserFactory() to construct a parser to load the package. + */ + function &fromXmlString($data, $state, $file, $archive = false) + { + if (preg_match('/<package[^>]+version=[\'"]([0-9]+\.[0-9]+)[\'"]/', $data, $packageversion)) { + if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) { + return PEAR::raiseError('package.xml version "' . $packageversion[1] . + '" is not supported, only 1.0, 2.0, and 2.1 are supported.'); + } + + $object = &$this->parserFactory($packageversion[1]); + if ($this->_logger) { + $object->setLogger($this->_logger); + } + + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) {; + if ($this->_config->get('verbose') > 0 + && $this->_logger && $pf->getValidationWarnings(false) + ) { + foreach ($pf->getValidationWarnings(false) as $warning) { + $this->_logger->log(0, 'ERROR: ' . $warning['message']); + } + } + + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } elseif (preg_match('/<package[^>]+version=[\'"]([^"\']+)[\'"]/', $data, $packageversion)) { + $a = PEAR::raiseError('package.xml file "' . $file . + '" has unsupported package.xml <package> version "' . $packageversion[1] . '"'); + return $a; + } else { + if (!class_exists('PEAR_ErrorStack')) { + require_once 'PEAR/ErrorStack.php'; + } + + PEAR_ErrorStack::staticPush('PEAR_PackageFile', + PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION, + 'warning', array('xml' => $data), 'package.xml "' . $file . + '" has no package.xml <package> version'); + $object = &$this->parserFactory('1.0'); + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) { + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * @return void + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Create a PEAR_PackageFile_v* from a compresed Tar or Tgz file. + * @access public + * @param string contents of package.xml file + * @param int package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @using Archive_Tar to extract the files + * @using fromPackageFile() to load the package after the package.xml + * file is extracted. + */ + function &fromTgzFile($file, $state) + { + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + + $tar = new Archive_Tar($file); + if ($this->_debug <= 1) { + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + } + + $content = $tar->listContent(); + if ($this->_debug <= 1) { + $tar->popErrorHandling(); + } + + if (!is_array($content)) { + if (is_string($file) && strlen($file < 255) && + (!file_exists($file) || !@is_file($file))) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $file = realpath($file); + $ret = PEAR::raiseError("Could not get contents of package \"$file\"". + '. Invalid tgz file.'); + return $ret; + } + + if (!count($content) && !@is_file($file)) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $xml = null; + $origfile = $file; + foreach ($content as $file) { + $name = $file['filename']; + if ($name == 'package2.xml') { // allow a .tgz to distribute both versions + $xml = $name; + break; + } + + if ($name == 'package.xml') { + $xml = $name; + break; + } elseif (preg_match('/package.xml$/', $name, $match)) { + $xml = $name; + break; + } + } + + $tmpdir = System::mktemp('-t "' . $this->_config->get('temp_dir') . '" -d pear'); + if ($tmpdir === false) { + $ret = PEAR::raiseError("there was a problem with getting the configured temp directory"); + return $ret; + } + + PEAR_PackageFile::addTempFile($tmpdir); + + $this->_extractErrors(); + PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors')); + + if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { + $extra = implode("\n", $this->_extractErrors()); + if ($extra) { + $extra = ' ' . $extra; + } + + PEAR::staticPopErrorHandling(); + $ret = PEAR::raiseError('could not extract the package.xml file from "' . + $origfile . '"' . $extra); + return $ret; + } + + PEAR::staticPopErrorHandling(); + $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile); + return $ret; + } + + /** + * helper callback for extracting Archive_Tar errors + * + * @param PEAR_Error|null $err + * @return array + * @access private + */ + function _extractErrors($err = null) + { + static $errors = array(); + if ($err === null) { + $e = $errors; + $errors = array(); + return $e; + } + $errors[] = $err->getMessage(); + } + + /** + * Create a PEAR_PackageFile_v* from a package.xml file. + * + * @access public + * @param string $descfile name of package xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string|false $archive name of the archive this package.xml came + * from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses PEAR_PackageFile::fromXmlString to create the oject after the + * XML is loaded from the package.xml file. + */ + function &fromPackageFile($descfile, $state, $archive = false) + { + $fp = false; + if (is_string($descfile) && strlen($descfile) < 255 && + ( + !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) + || (!$fp = @fopen($descfile, 'r')) + ) + ) { + $a = PEAR::raiseError("Unable to open $descfile"); + return $a; + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive); + return $ret; + } + + /** + * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file. + * + * This method is able to extract information about a package from a .tgz + * archive or from a XML package definition file. + * + * @access public + * @param string $info file name + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses fromPackageFile() if the file appears to be XML + * @uses fromTgzFile() to load all non-XML files + */ + function &fromAnyFile($info, $state) + { + if (is_dir($info)) { + $dir_name = realpath($info); + if (file_exists($dir_name . '/package.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state); + } elseif (file_exists($dir_name . '/package2.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state); + } else { + $info = PEAR::raiseError("No package definition found in '$info' directory"); + } + + return $info; + } + + $fp = false; + if (is_string($info) && strlen($info) < 255 && + (file_exists($info) || ($fp = @fopen($info, 'r'))) + ) { + + if ($fp) { + fclose($fp); + } + + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = &PEAR_PackageFile::fromPackageFile($info, $state); + } elseif ($tmp == '.tar' || $tmp == '.tgz') { + $info = &PEAR_PackageFile::fromTgzFile($info, $state); + } else { + $fp = fopen($info, 'r'); + $test = fread($fp, 5); + fclose($fp); + if ($test == '<?xml') { + $info = &PEAR_PackageFile::fromPackageFile($info, $state); + } else { + $info = &PEAR_PackageFile::fromTgzFile($info, $state); + } + } + + return $info; + } + + $info = PEAR::raiseError("Cannot open '$info' for parsing"); + return $info; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/PackageFile/Generator/v1.php b/includes/pear/PEAR/PackageFile/Generator/v1.php new file mode 100644 index 0000000..fdfdecd --- /dev/null +++ b/includes/pear/PEAR/PackageFile/Generator/v1.php @@ -0,0 +1,1284 @@ +<?php +/** + * package.xml generation class, package.xml version 1.0 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +require_once 'System.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * This class converts a PEAR_PackageFile_v1 object into any output format. + * + * Supported output formats include array, XML string, and a PEAR_PackageFile_v2 + * object, for converting package.xml 1.0 into package.xml 2.0 with no sweat. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v1 +{ + /** + * @var PEAR_PackageFile_v1 + */ + var $_packagefile; + function PEAR_PackageFile_Generator_v1(&$packagefile) + { + $this->_packagefile = &$packagefile; + } + + function getPackagerVersion() + { + return '1.9.4'; + } + + /** + * @param PEAR_Packager + * @param bool if true, a .tgz is written, otherwise a .tar is written + * @param string|null directory in which to save the .tgz + * @return string|PEAR_Error location of package or error object + */ + function toTgz(&$packager, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: "' . $where . '" could' . + ' not be created'); + } + if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') && + !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: unable to save package.xml as' . + ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"'); + } + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: invalid package file'); + } + $pkginfo = $this->_packagefile->getArray(); + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $pkginfo['package'] . '-' . $pkginfo['version']; + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) && + !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: cannot create tgz file "' . + getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext . '"'); + } + if ($pkgfile = $this->_packagefile->getPackageFile()) { + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + } else { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: package file object must ' . + 'be created from a real file'); + } + // {{{ Create the package file list + $filelist = array(); + $i = 0; + + foreach ($this->_packagefile->getFilelist() as $fname => $atts) { + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return PEAR::raiseError("File does not exist: $fname"); + } else { + $filelist[$i++] = $file; + if (!isset($atts['md5sum'])) { + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($file)); + } + $packager->log(2, "Adding file $fname"); + } + } + // }}} + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $ok; + } elseif (!$ok) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $pkgdir)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + return $dest_package; + } + } + + /** + * @param string|null directory to place the package.xml in, or null for a temporary dir + * @param int one of the PEAR_VALIDATE_* constants + * @param string name of the generated file + * @param bool if true, then no analysis will be performed on role="php" files + * @return string|PEAR_Error path to the created file on success + */ + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml', + $nofilechecking = false) + { + if (!$this->_packagefile->validate($state, $nofilechecking)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state, true)); + fclose($np); + return $newpkgfile; + } + + /** + * fix both XML encoding to be UTF8, and replace standard XML entities < > " & ' + * + * @param string $string + * @return string + * @access private + */ + function _fixXmlEncoding($string) + { + if (version_compare(phpversion(), '5.0.0', 'lt')) { + $string = utf8_encode($string); + } + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false) + { + $this->_packagefile->setDate(date('Y-m-d')); + if (!$this->_packagefile->validate($state, $nofilevalidation)) { + return false; + } + $pkginfo = $this->_packagefile->getArray(); + static $maint_map = array( + "handle" => "user", + "name" => "name", + "email" => "email", + "role" => "role", + ); + $ret = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"; + $ret .= "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n"; + $ret .= "<package version=\"1.0\" packagerversion=\"1.9.4\">\n" . +" <name>$pkginfo[package]</name>"; + if (isset($pkginfo['extends'])) { + $ret .= "\n<extends>$pkginfo[extends]</extends>"; + } + $ret .= + "\n <summary>".$this->_fixXmlEncoding($pkginfo['summary'])."</summary>\n" . +" <description>".trim($this->_fixXmlEncoding($pkginfo['description']))."\n </description>\n" . +" <maintainers>\n"; + foreach ($pkginfo['maintainers'] as $maint) { + $ret .= " <maintainer>\n"; + foreach ($maint_map as $idx => $elm) { + $ret .= " <$elm>"; + $ret .= $this->_fixXmlEncoding($maint[$idx]); + $ret .= "</$elm>\n"; + } + $ret .= " </maintainer>\n"; + } + $ret .= " </maintainers>\n"; + $ret .= $this->_makeReleaseXml($pkginfo, false, $state); + if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) { + $ret .= " <changelog>\n"; + foreach ($pkginfo['changelog'] as $oldrelease) { + $ret .= $this->_makeReleaseXml($oldrelease, true); + } + $ret .= " </changelog>\n"; + } + $ret .= "</package>\n"; + return $ret; + } + + // }}} + // {{{ _makeReleaseXml() + + /** + * Generate part of an XML description with release information. + * + * @param array $pkginfo array with release information + * @param bool $changelog whether the result will be in a changelog element + * + * @return string XML data + * + * @access private + */ + function _makeReleaseXml($pkginfo, $changelog = false, $state = PEAR_VALIDATE_NORMAL) + { + // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!! + $indent = $changelog ? " " : ""; + $ret = "$indent <release>\n"; + if (!empty($pkginfo['version'])) { + $ret .= "$indent <version>$pkginfo[version]</version>\n"; + } + if (!empty($pkginfo['release_date'])) { + $ret .= "$indent <date>$pkginfo[release_date]</date>\n"; + } + if (!empty($pkginfo['release_license'])) { + $ret .= "$indent <license>$pkginfo[release_license]</license>\n"; + } + if (!empty($pkginfo['release_state'])) { + $ret .= "$indent <state>$pkginfo[release_state]</state>\n"; + } + if (!empty($pkginfo['release_notes'])) { + $ret .= "$indent <notes>".trim($this->_fixXmlEncoding($pkginfo['release_notes'])) + ."\n$indent </notes>\n"; + } + if (!empty($pkginfo['release_warnings'])) { + $ret .= "$indent <warnings>".$this->_fixXmlEncoding($pkginfo['release_warnings'])."</warnings>\n"; + } + if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) { + $ret .= "$indent <deps>\n"; + foreach ($pkginfo['release_deps'] as $dep) { + $ret .= "$indent <dep type=\"$dep[type]\" rel=\"$dep[rel]\""; + if (isset($dep['version'])) { + $ret .= " version=\"$dep[version]\""; + } + if (isset($dep['optional'])) { + $ret .= " optional=\"$dep[optional]\""; + } + if (isset($dep['name'])) { + $ret .= ">$dep[name]</dep>\n"; + } else { + $ret .= "/>\n"; + } + } + $ret .= "$indent </deps>\n"; + } + if (isset($pkginfo['configure_options'])) { + $ret .= "$indent <configureoptions>\n"; + foreach ($pkginfo['configure_options'] as $c) { + $ret .= "$indent <configureoption name=\"". + $this->_fixXmlEncoding($c['name']) . "\""; + if (isset($c['default'])) { + $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\""; + } + $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\""; + $ret .= "/>\n"; + } + $ret .= "$indent </configureoptions>\n"; + } + if (isset($pkginfo['provides'])) { + foreach ($pkginfo['provides'] as $key => $what) { + $ret .= "$indent <provides type=\"$what[type]\" "; + $ret .= "name=\"$what[name]\" "; + if (isset($what['extends'])) { + $ret .= "extends=\"$what[extends]\" "; + } + $ret .= "/>\n"; + } + } + if (isset($pkginfo['filelist'])) { + $ret .= "$indent <filelist>\n"; + if ($state ^ PEAR_VALIDATE_PACKAGING) { + $ret .= $this->recursiveXmlFilelist($pkginfo['filelist']); + } else { + foreach ($pkginfo['filelist'] as $file => $fa) { + if (!isset($fa['role'])) { + $fa['role'] = ''; + } + $ret .= "$indent <file role=\"$fa[role]\""; + if (isset($fa['baseinstalldir'])) { + $ret .= ' baseinstalldir="' . + $this->_fixXmlEncoding($fa['baseinstalldir']) . '"'; + } + if (isset($fa['md5sum'])) { + $ret .= " md5sum=\"$fa[md5sum]\""; + } + if (isset($fa['platform'])) { + $ret .= " platform=\"$fa[platform]\""; + } + if (!empty($fa['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($fa['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($fa['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($fa['replacements'] as $r) { + $ret .= "$indent <replace"; + foreach ($r as $k => $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent </file>\n"; + } + } + } + $ret .= "$indent </filelist>\n"; + } + $ret .= "$indent </release>\n"; + return $ret; + } + + /** + * @param array + * @access protected + */ + function recursiveXmlFilelist($list) + { + $this->_dirs = array(); + foreach ($list as $file => $attributes) { + $this->_addDir($this->_dirs, explode('/', dirname($file)), $file, $attributes); + } + return $this->_formatDir($this->_dirs); + } + + /** + * @param array + * @param array + * @param string|null + * @param array|null + * @access private + */ + function _addDir(&$dirs, $dir, $file = null, $attributes = null) + { + if ($dir == array() || $dir == array('.')) { + $dirs['files'][basename($file)] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dirs'][$curdir])) { + $dirs['dirs'][$curdir] = array(); + } + $this->_addDir($dirs['dirs'][$curdir], $dir, $file, $attributes); + } + + /** + * @param array + * @param string + * @param string + * @access private + */ + function _formatDir($dirs, $indent = '', $curdir = '') + { + $ret = ''; + if (!count($dirs)) { + return ''; + } + if (isset($dirs['dirs'])) { + uksort($dirs['dirs'], 'strnatcasecmp'); + foreach ($dirs['dirs'] as $dir => $contents) { + $usedir = "$curdir/$dir"; + $ret .= "$indent <dir name=\"$dir\">\n"; + $ret .= $this->_formatDir($contents, "$indent ", $usedir); + $ret .= "$indent </dir> <!-- $usedir -->\n"; + } + } + if (isset($dirs['files'])) { + uksort($dirs['files'], 'strnatcasecmp'); + foreach ($dirs['files'] as $file => $attribs) { + $ret .= $this->_formatFile($file, $attribs, $indent); + } + } + return $ret; + } + + /** + * @param string + * @param array + * @param string + * @access private + */ + function _formatFile($file, $attributes, $indent) + { + $ret = "$indent <file role=\"$attributes[role]\""; + if (isset($attributes['baseinstalldir'])) { + $ret .= ' baseinstalldir="' . + $this->_fixXmlEncoding($attributes['baseinstalldir']) . '"'; + } + if (isset($attributes['md5sum'])) { + $ret .= " md5sum=\"$attributes[md5sum]\""; + } + if (isset($attributes['platform'])) { + $ret .= " platform=\"$attributes[platform]\""; + } + if (!empty($attributes['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($attributes['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($attributes['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($attributes['replacements'] as $r) { + $ret .= "$indent <replace"; + foreach ($r as $k => $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent </file>\n"; + } + return $ret; + } + + // {{{ _unIndent() + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * @return array + */ + function dependenciesToV2() + { + $arr = array(); + $this->_convertDependencies2_0($arr); + return $arr['dependencies']; + } + + /** + * Convert a package.xml version 1.0 into version 2.0 + * + * Note that this does a basic conversion, to allow more advanced + * features like bundles and multiple releases + * @param string the classname to instantiate and return. This must be + * PEAR_PackageFile_v2 or a descendant + * @param boolean if true, only valid, deterministic package.xml 1.0 as defined by the + * strictest parameters will be converted + * @return PEAR_PackageFile_v2|PEAR_Error + */ + function &toV2($class = 'PEAR_PackageFile_v2', $strict = false) + { + if ($strict) { + if (!$this->_packagefile->validate()) { + $a = PEAR::raiseError('invalid package.xml version 1.0 cannot be converted' . + ' to version 2.0', null, null, null, + $this->_packagefile->getValidationWarnings(true)); + return $a; + } + } + + $arr = array( + 'attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => "http://pear.php.net/dtd/tasks-1.0\n" . +"http://pear.php.net/dtd/tasks-1.0.xsd\n" . +"http://pear.php.net/dtd/package-2.0\n" . +'http://pear.php.net/dtd/package-2.0.xsd', + ), + 'name' => $this->_packagefile->getPackage(), + 'channel' => 'pear.php.net', + ); + $arr['summary'] = $this->_packagefile->getSummary(); + $arr['description'] = $this->_packagefile->getDescription(); + $maintainers = $this->_packagefile->getMaintainers(); + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] != 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr['lead'][] = $new; + } + + if (!isset($arr['lead'])) { // some people... you know? + $arr['lead'] = array( + 'name' => 'unknown', + 'user' => 'unknown', + 'email' => 'noleadmaintainer@example.com', + 'active' => 'no', + ); + } + + if (count($arr['lead']) == 1) { + $arr['lead'] = $arr['lead'][0]; + } + + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] == 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr[$maintainer['role']][] = $new; + } + + if (isset($arr['developer']) && count($arr['developer']) == 1) { + $arr['developer'] = $arr['developer'][0]; + } + + if (isset($arr['contributor']) && count($arr['contributor']) == 1) { + $arr['contributor'] = $arr['contributor'][0]; + } + + if (isset($arr['helper']) && count($arr['helper']) == 1) { + $arr['helper'] = $arr['helper'][0]; + } + + $arr['date'] = $this->_packagefile->getDate(); + $arr['version'] = + array( + 'release' => $this->_packagefile->getVersion(), + 'api' => $this->_packagefile->getVersion(), + ); + $arr['stability'] = + array( + 'release' => $this->_packagefile->getState(), + 'api' => $this->_packagefile->getState(), + ); + $licensemap = + array( + 'php' => 'http://www.php.net/license', + 'php license' => 'http://www.php.net/license', + 'lgpl' => 'http://www.gnu.org/copyleft/lesser.html', + 'bsd' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd-style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'mit' => 'http://www.opensource.org/licenses/mit-license.php', + 'gpl' => 'http://www.gnu.org/copyleft/gpl.html', + 'apache' => 'http://www.opensource.org/licenses/apache2.0.php' + ); + + if (isset($licensemap[strtolower($this->_packagefile->getLicense())])) { + $arr['license'] = array( + 'attribs' => array('uri' => + $licensemap[strtolower($this->_packagefile->getLicense())]), + '_content' => $this->_packagefile->getLicense() + ); + } else { + // don't use bogus uri + $arr['license'] = $this->_packagefile->getLicense(); + } + + $arr['notes'] = $this->_packagefile->getNotes(); + $temp = array(); + $arr['contents'] = $this->_convertFilelist2_0($temp); + $this->_convertDependencies2_0($arr); + $release = ($this->_packagefile->getConfigureOptions() || $this->_isExtension) ? + 'extsrcrelease' : 'phprelease'; + if ($release == 'extsrcrelease') { + $arr['channel'] = 'pecl.php.net'; + $arr['providesextension'] = $arr['name']; // assumption + } + + $arr[$release] = array(); + if ($this->_packagefile->getConfigureOptions()) { + $arr[$release]['configureoption'] = $this->_packagefile->getConfigureOptions(); + foreach ($arr[$release]['configureoption'] as $i => $opt) { + $arr[$release]['configureoption'][$i] = array('attribs' => $opt); + } + if (count($arr[$release]['configureoption']) == 1) { + $arr[$release]['configureoption'] = $arr[$release]['configureoption'][0]; + } + } + + $this->_convertRelease2_0($arr[$release], $temp); + if ($release == 'extsrcrelease' && count($arr[$release]) > 1) { + // multiple extsrcrelease tags added in PEAR 1.4.1 + $arr['dependencies']['required']['pearinstaller']['min'] = '1.4.1'; + } + + if ($cl = $this->_packagefile->getChangelog()) { + foreach ($cl as $release) { + $rel = array(); + $rel['version'] = + array( + 'release' => $release['version'], + 'api' => $release['version'], + ); + if (!isset($release['release_state'])) { + $release['release_state'] = 'stable'; + } + + $rel['stability'] = + array( + 'release' => $release['release_state'], + 'api' => $release['release_state'], + ); + if (isset($release['release_date'])) { + $rel['date'] = $release['release_date']; + } else { + $rel['date'] = date('Y-m-d'); + } + + if (isset($release['release_license'])) { + if (isset($licensemap[strtolower($release['release_license'])])) { + $uri = $licensemap[strtolower($release['release_license'])]; + } else { + $uri = 'http://www.example.com'; + } + $rel['license'] = array( + 'attribs' => array('uri' => $uri), + '_content' => $release['release_license'] + ); + } else { + $rel['license'] = $arr['license']; + } + + if (!isset($release['release_notes'])) { + $release['release_notes'] = 'no release notes'; + } + + $rel['notes'] = $release['release_notes']; + $arr['changelog']['release'][] = $rel; + } + } + + $ret = new $class; + $ret->setConfig($this->_packagefile->_config); + if (isset($this->_packagefile->_logger) && is_object($this->_packagefile->_logger)) { + $ret->setLogger($this->_packagefile->_logger); + } + + $ret->fromArray($arr); + return $ret; + } + + /** + * @param array + * @param bool + * @access private + */ + function _convertDependencies2_0(&$release, $internal = false) + { + $peardep = array('pearinstaller' => + array('min' => '1.4.0b1')); // this is a lot safer + $required = $optional = array(); + $release['dependencies'] = array('required' => array()); + if ($this->_packagefile->hasDeps()) { + foreach ($this->_packagefile->getDeps() as $dep) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + $required[] = $dep; + } else { + $optional[] = $dep; + } + } + foreach (array('required', 'optional') as $arr) { + $deps = array(); + foreach ($$arr as $dep) { + // organize deps by dependency type and name + if (!isset($deps[$dep['type']])) { + $deps[$dep['type']] = array(); + } + if (isset($dep['name'])) { + $deps[$dep['type']][$dep['name']][] = $dep; + } else { + $deps[$dep['type']][] = $dep; + } + } + do { + if (isset($deps['php'])) { + $php = array(); + if (count($deps['php']) > 1) { + $php = $this->_processPhpDeps($deps['php']); + } else { + if (!isset($deps['php'][0])) { + list($key, $blah) = each ($deps['php']); // stupid buggy versions + $deps['php'] = array($blah[0]); + } + $php = $this->_processDep($deps['php'][0]); + if (!$php) { + break; // poor mans throw + } + } + $release['dependencies'][$arr]['php'] = $php; + } + } while (false); + do { + if (isset($deps['pkg'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['pkg']); + if (!$pkg) { + break; // poor mans throw + } + $release['dependencies'][$arr]['package'] = $pkg; + } + } while (false); + do { + if (isset($deps['ext'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['ext']); + $release['dependencies'][$arr]['extension'] = $pkg; + } + } while (false); + // skip sapi - it's not supported so nobody will have used it + // skip os - it's not supported in 1.0 + } + } + if (isset($release['dependencies']['required'])) { + $release['dependencies']['required'] = + array_merge($peardep, $release['dependencies']['required']); + } else { + $release['dependencies']['required'] = $peardep; + } + if (!isset($release['dependencies']['required']['php'])) { + $release['dependencies']['required']['php'] = + array('min' => '4.0.0'); + } + $order = array(); + $bewm = $release['dependencies']['required']; + $order['php'] = $bewm['php']; + $order['pearinstaller'] = $bewm['pearinstaller']; + isset($bewm['package']) ? $order['package'] = $bewm['package'] :0; + isset($bewm['extension']) ? $order['extension'] = $bewm['extension'] :0; + $release['dependencies']['required'] = $order; + } + + /** + * @param array + * @access private + */ + function _convertFilelist2_0(&$package) + { + $ret = array('dir' => + array( + 'attribs' => array('name' => '/'), + 'file' => array() + ) + ); + $package['platform'] = + $package['install-as'] = array(); + $this->_isExtension = false; + foreach ($this->_packagefile->getFilelist() as $name => $file) { + $file['name'] = $name; + if (isset($file['role']) && $file['role'] == 'src') { + $this->_isExtension = true; + } + if (isset($file['replacements'])) { + $repl = $file['replacements']; + unset($file['replacements']); + } else { + unset($repl); + } + if (isset($file['install-as'])) { + $package['install-as'][$name] = $file['install-as']; + unset($file['install-as']); + } + if (isset($file['platform'])) { + $package['platform'][$name] = $file['platform']; + unset($file['platform']); + } + $file = array('attribs' => $file); + if (isset($repl)) { + foreach ($repl as $replace ) { + $file['tasks:replace'][] = array('attribs' => $replace); + } + if (count($repl) == 1) { + $file['tasks:replace'] = $file['tasks:replace'][0]; + } + } + $ret['dir']['file'][] = $file; + } + return $ret; + } + + /** + * Post-process special files with install-as/platform attributes and + * make the release tag. + * + * This complex method follows this work-flow to create the release tags: + * + * <pre> + * - if any install-as/platform exist, create a generic release and fill it with + * o <install as=..> tags for <file name=... install-as=...> + * o <install as=..> tags for <file name=... platform=!... install-as=..> + * o <ignore> tags for <file name=... platform=...> + * o <ignore> tags for <file name=... platform=... install-as=..> + * - create a release for each platform encountered and fill with + * o <install as..> tags for <file name=... install-as=...> + * o <install as..> tags for <file name=... platform=this platform install-as=..> + * o <install as..> tags for <file name=... platform=!other platform install-as=..> + * o <ignore> tags for <file name=... platform=!this platform> + * o <ignore> tags for <file name=... platform=other platform> + * o <ignore> tags for <file name=... platform=other platform install-as=..> + * o <ignore> tags for <file name=... platform=!this platform install-as=..> + * </pre> + * + * It does this by accessing the $package parameter, which contains an array with + * indices: + * + * - platform: mapping of file => OS the file should be installed on + * - install-as: mapping of file => installed name + * - osmap: mapping of OS => list of files that should be installed + * on that OS + * - notosmap: mapping of OS => list of files that should not be + * installed on that OS + * + * @param array + * @param array + * @access private + */ + function _convertRelease2_0(&$release, $package) + { + //- if any install-as/platform exist, create a generic release and fill it with + if (count($package['platform']) || count($package['install-as'])) { + $generic = array(); + $genericIgnore = array(); + foreach ($package['install-as'] as $file => $as) { + //o <install as=..> tags for <file name=... install-as=...> + if (!isset($package['platform'][$file])) { + $generic[] = $file; + continue; + } + //o <install as=..> tags for <file name=... platform=!... install-as=..> + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} == '!') { + $generic[] = $file; + continue; + } + //o <ignore> tags for <file name=... platform=... install-as=..> + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!') { + $genericIgnore[] = $file; + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + if ($platform{0} != '!') { + //o <ignore> tags for <file name=... platform=...> + $genericIgnore[] = $file; + } + } + if (count($package['platform'])) { + $oses = $notplatform = $platform = array(); + foreach ($package['platform'] as $file => $os) { + // get a list of oses + if ($os{0} == '!') { + if (isset($oses[substr($os, 1)])) { + continue; + } + $oses[substr($os, 1)] = count($oses); + } else { + if (isset($oses[$os])) { + continue; + } + $oses[$os] = count($oses); + } + } + //- create a release for each platform encountered and fill with + foreach ($oses as $os => $releaseNum) { + $release[$releaseNum]['installconditions']['os']['name'] = $os; + $release[$releaseNum]['filelist'] = array('install' => array(), + 'ignore' => array()); + foreach ($package['install-as'] as $file => $as) { + //o <install as=..> tags for <file name=... install-as=...> + if (!isset($package['platform'][$file])) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o <install as..> tags for + // <file name=... platform=this platform install-as=..> + if (isset($package['platform'][$file]) && + $package['platform'][$file] == $os) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o <install as..> tags for + // <file name=... platform=!other platform install-as=..> + if (isset($package['platform'][$file]) && + $package['platform'][$file] != "!$os" && + $package['platform'][$file]{0} == '!') { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o <ignore> tags for + // <file name=... platform=!this platform install-as=..> + if (isset($package['platform'][$file]) && + $package['platform'][$file] == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o <ignore> tags for + // <file name=... platform=other platform install-as=..> + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!' && + $package['platform'][$file] != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + //o <ignore> tags for <file name=... platform=!this platform> + if ($platform == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o <ignore> tags for <file name=... platform=other platform> + if ($platform{0} != '!' && $platform != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + } + } + if (!count($release[$releaseNum]['filelist']['install'])) { + unset($release[$releaseNum]['filelist']['install']); + } + if (!count($release[$releaseNum]['filelist']['ignore'])) { + unset($release[$releaseNum]['filelist']['ignore']); + } + } + if (count($generic) || count($genericIgnore)) { + $release[count($oses)] = array(); + if (count($generic)) { + foreach ($generic as $file) { + if (isset($package['install-as'][$file])) { + $installas = $package['install-as'][$file]; + } else { + $installas = $file; + } + $release[count($oses)]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $installas, + ) + ); + } + } + if (count($genericIgnore)) { + foreach ($genericIgnore as $file) { + $release[count($oses)]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ) + ); + } + } + } + // cleanup + foreach ($release as $i => $rel) { + if (isset($rel['filelist']['install']) && + count($rel['filelist']['install']) == 1) { + $release[$i]['filelist']['install'] = + $release[$i]['filelist']['install'][0]; + } + if (isset($rel['filelist']['ignore']) && + count($rel['filelist']['ignore']) == 1) { + $release[$i]['filelist']['ignore'] = + $release[$i]['filelist']['ignore'][0]; + } + } + if (count($release) == 1) { + $release = $release[0]; + } + } else { + // no platform atts, but some install-as atts + foreach ($package['install-as'] as $file => $value) { + $release['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $value + ) + ); + } + if (count($release['filelist']['install']) == 1) { + $release['filelist']['install'] = $release['filelist']['install'][0]; + } + } + } + } + + /** + * @param array + * @return array + * @access private + */ + function _processDep($dep) + { + if ($dep['type'] == 'php') { + if ($dep['rel'] == 'has') { + // come on - everyone has php! + return false; + } + } + $php = array(); + if ($dep['type'] != 'php') { + $php['name'] = $dep['name']; + if ($dep['type'] == 'pkg') { + $php['channel'] = 'pear.php.net'; + } + } + switch ($dep['rel']) { + case 'gt' : + $php['min'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'ge' : + if (!isset($dep['version'])) { + if ($dep['type'] == 'php') { + if (isset($dep['name'])) { + $dep['version'] = $dep['name']; + } + } + } + $php['min'] = $dep['version']; + break; + case 'lt' : + $php['max'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'le' : + $php['max'] = $dep['version']; + break; + case 'eq' : + $php['min'] = $dep['version']; + $php['max'] = $dep['version']; + break; + case 'ne' : + $php['exclude'] = $dep['version']; + break; + case 'not' : + $php['conflicts'] = 'yes'; + break; + } + return $php; + } + + /** + * @param array + * @return array + */ + function _processPhpDeps($deps) + { + $test = array(); + foreach ($deps as $dep) { + $test[] = $this->_processDep($dep); + } + $min = array(); + $max = array(); + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + return $php; + } + + /** + * process multiple dependencies that have a name, like package deps + * @param array + * @return array + * @access private + */ + function _processMultipleDepsName($deps) + { + $ret = $tests = array(); + foreach ($deps as $name => $dep) { + foreach ($dep as $d) { + $tests[$name][] = $this->_processDep($d); + } + } + + foreach ($tests as $name => $test) { + $max = $min = $php = array(); + $php['name'] = $name; + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['channel'])) { + $php['channel'] = 'pear.php.net'; + } + if (isset($dep['conflicts']) && $dep['conflicts'] == 'yes') { + $php['conflicts'] = 'yes'; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + $ret[] = $php; + } + return $ret; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/PackageFile/Generator/v2.php b/includes/pear/PEAR/PackageFile/Generator/v2.php new file mode 100644 index 0000000..8ff90e2 --- /dev/null +++ b/includes/pear/PEAR/PackageFile/Generator/v2.php @@ -0,0 +1,893 @@ +<?php +/** + * package.xml generation class, package.xml version 2.0 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * file/dir manipulation routines + */ +require_once 'System.php'; +require_once 'XML/Util.php'; + +/** + * This class converts a PEAR_PackageFile_v2 object into any output format. + * + * Supported output formats include array, XML string (using S. Schmidt's + * XML_Serializer, slightly customized) + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v2 +{ + /** + * default options for the serialization + * @access private + * @var array $_defaultOptions + */ + var $_defaultOptions = array( + 'indent' => ' ', // string used for indentation + 'linebreak' => "\n", // string used for newlines + 'typeHints' => false, // automatically add type hin attributes + 'addDecl' => true, // add an XML declaration + 'defaultTagName' => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names + 'classAsTagName' => false, // use classname for objects in indexed arrays + 'keyAttribute' => '_originalKey', // attribute where original key is stored + 'typeAttribute' => '_type', // attribute for type (only if typeHints => true) + 'classAttribute' => '_class', // attribute for class of objects (only if typeHints => true) + 'scalarAsAttributes' => false, // scalar values (strings, ints,..) will be serialized as attribute + 'prependAttributes' => '', // prepend string for attributes + 'indentAttributes' => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column + 'mode' => 'simplexml', // use 'simplexml' to use parent name as tagname if transforming an indexed array + 'addDoctype' => false, // add a doctype declaration + 'doctype' => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()} + 'rootName' => 'package', // name of the root tag + 'rootAttributes' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 +http://pear.php.net/dtd/tasks-1.0.xsd +http://pear.php.net/dtd/package-2.0 +http://pear.php.net/dtd/package-2.0.xsd', + ), // attributes of the root tag + 'attributesArray' => 'attribs', // all values in this key will be treated as attributes + 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + 'beautifyFilelist' => false, + 'encoding' => 'UTF-8', + ); + + /** + * options for the serialization + * @access private + * @var array $options + */ + var $options = array(); + + /** + * current tag depth + * @var integer $_tagDepth + */ + var $_tagDepth = 0; + + /** + * serilialized representation of the data + * @var string $_serializedData + */ + var $_serializedData = null; + /** + * @var PEAR_PackageFile_v2 + */ + var $_packagefile; + /** + * @param PEAR_PackageFile_v2 + */ + function PEAR_PackageFile_Generator_v2(&$packagefile) + { + $this->_packagefile = &$packagefile; + if (isset($this->_packagefile->encoding)) { + $this->_defaultOptions['encoding'] = $this->_packagefile->encoding; + } + } + + /** + * @return string + */ + function getPackagerVersion() + { + return '1.9.4'; + } + + /** + * @param PEAR_Packager + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz(&$packager, $compress = true, $where = null) + { + $a = null; + return $this->toTgz2($packager, $a, $compress, $where); + } + + /** + * Package up both a package.xml and package2.xml for the same release + * @param PEAR_Packager + * @param PEAR_PackageFile_v1 + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz2(&$packager, &$pf1, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if (!$this->_packagefile->isEquivalent($pf1)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . + basename($pf1->getPackageFile()) . + '" is not equivalent to "' . basename($this->_packagefile->getPackageFile()) + . '"'); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' . + ' not be created'); + } + + $file = $where . DIRECTORY_SEPARATOR . 'package.xml'; + if (file_exists($file) && !is_file($file)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' . + ' "' . $file .'"'); + } + + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml'); + } + + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion(); + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists($dest_package) && !is_file($dest_package)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' . + $dest_package . '"'); + } + + $pkgfile = $this->_packagefile->getPackageFile(); + if (!$pkgfile) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' . + 'be created from a real file'); + } + + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + + // {{{ Create the package file list + $filelist = array(); + $i = 0; + $this->_packagefile->flattenFilelist(); + $contents = $this->_packagefile->getContents(); + if (isset($contents['bundledpackage'])) { // bundles of packages + $contents = $contents['bundledpackage']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $package) { + $fname = $package; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + $filelist[$i++] = $tfile; + $packager->log(2, "Adding package $fname"); + } + } else { // normal packages + $contents = $contents['dir']['file']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $file) { + $fname = $file['attribs']['name']; + $atts = $file['attribs']; + $orig = $file; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $origperms = fileperms($file); + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + unset($orig['attribs']); + if (count($orig)) { // file with tasks + // run any package-time tasks + $contents = file_get_contents($file); + foreach ($orig as $tag => $raw) { + $tag = str_replace( + array($this->_packagefile->getTasksNs() . ':', '-'), + array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->_packagefile->_config, + $this->_packagefile->_logger, + PEAR_TASK_PACKAGE); + $task->init($raw, $atts, null); + $res = $task->startSession($this->_packagefile, $contents, $tfile); + if (!$res) { + continue; // skip this task + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + System::mkdir(array('-p', dirname($tfile))); + $wp = fopen($tfile, "wb"); + fwrite($wp, $contents); + fclose($wp); + } + } + + if (!file_exists($tfile)) { + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + } + + chmod($tfile, $origperms); + $filelist[$i++] = $tfile; + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1); + $packager->log(2, "Adding file $fname"); + } + } + // }}} + + $name = $pf1 !== null ? 'package2.xml' : 'package.xml'; + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $packager->raiseError($ok); + } elseif (!$ok) { + return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name . + ' failed'); + } + + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): tarball creation failed'); + } + + // add the package.xml version 1.0 + if ($pf1 !== null) { + $pfgen = &$pf1->getDefaultGenerator(); + $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if (!$tar->addModify(array($packagexml1), '', $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): adding package.xml failed'); + } + } + + return $dest_package; + } + } + + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml') + { + if (!$this->_packagefile->validate($state)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state)); + fclose($np); + return $newpkgfile; + } + + function &toV2() + { + return $this->_packagefile; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $options = array()) + { + $this->_packagefile->setDate(date('Y-m-d')); + $this->_packagefile->setTime(date('H:i:s')); + if (!$this->_packagefile->validate($state)) { + return false; + } + + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + + $arr = $this->_packagefile->getArray(); + if (isset($arr['filelist'])) { + unset($arr['filelist']); + } + + if (isset($arr['_lastversion'])) { + unset($arr['_lastversion']); + } + + // Fix the notes a little bit + if (isset($arr['notes'])) { + // This trims out the indenting, needs fixing + $arr['notes'] = "\n" . trim($arr['notes']) . "\n"; + } + + if (isset($arr['changelog']) && !empty($arr['changelog'])) { + // Fix for inconsistency how the array is filled depending on the changelog release amount + if (!isset($arr['changelog']['release'][0])) { + $release = $arr['changelog']['release']; + unset($arr['changelog']['release']); + + $arr['changelog']['release'] = array(); + $arr['changelog']['release'][0] = $release; + } + + foreach (array_keys($arr['changelog']['release']) as $key) { + $c =& $arr['changelog']['release'][$key]; + if (isset($c['notes'])) { + // This trims out the indenting, needs fixing + $c['notes'] = "\n" . trim($c['notes']) . "\n"; + } + } + } + + if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) { + $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']); + unset($arr['contents']['dir']['file']); + if (isset($use['dir'])) { + $arr['contents']['dir']['dir'] = $use['dir']; + } + if (isset($use['file'])) { + $arr['contents']['dir']['file'] = $use['file']; + } + $this->options['beautifyFilelist'] = true; + } + + $arr['attribs']['packagerversion'] = '1.9.4'; + if ($this->serialize($arr, $options)) { + return $this->_serializedData . "\n"; + } + + return false; + } + + + function _recursiveXmlFilelist($list) + { + $dirs = array(); + if (isset($list['attribs'])) { + $file = $list['attribs']['name']; + unset($list['attribs']['name']); + $attributes = $list['attribs']; + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes); + } else { + foreach ($list as $a) { + $file = $a['attribs']['name']; + $attributes = $a['attribs']; + unset($a['attribs']); + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a); + } + } + $this->_formatDir($dirs); + $this->_deFormat($dirs); + return $dirs; + } + + function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null) + { + if (!$tasks) { + $tasks = array(); + } + if ($dir == array() || $dir == array('.')) { + $dirs['file'][basename($file)] = $tasks; + $attributes['name'] = basename($file); + $dirs['file'][basename($file)]['attribs'] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dir'][$curdir])) { + $dirs['dir'][$curdir] = array(); + } + $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks); + } + + function _formatDir(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + $newdirs['dir'] = $dirs['dir']; + } + if (isset($dirs['file'])) { + $newdirs['file'] = $dirs['file']; + } + $dirs = $newdirs; + if (isset($dirs['dir'])) { + uksort($dirs['dir'], 'strnatcasecmp'); + foreach ($dirs['dir'] as $dir => $contents) { + $this->_formatDir($dirs['dir'][$dir]); + } + } + if (isset($dirs['file'])) { + uksort($dirs['file'], 'strnatcasecmp'); + }; + } + + function _deFormat(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + foreach ($dirs['dir'] as $dir => $contents) { + $newdir = array(); + $newdir['attribs']['name'] = $dir; + $this->_deFormat($contents); + foreach ($contents as $tag => $val) { + $newdir[$tag] = $val; + } + $newdirs['dir'][] = $newdir; + } + if (count($newdirs['dir']) == 1) { + $newdirs['dir'] = $newdirs['dir'][0]; + } + } + if (isset($dirs['file'])) { + foreach ($dirs['file'] as $name => $file) { + $newdirs['file'][] = $file; + } + if (count($newdirs['file']) == 1) { + $newdirs['file'] = $newdirs['file'][0]; + } + } + $dirs = $newdirs; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Unserializer() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Serializer() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @access public + * @param mixed $data data to serialize + * @return boolean true on success, pear error on failure + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array'; + $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']); + } + + // add doctype declaration + if ($this->options['addDoctype'] === true) { + $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype']) + . $this->options['linebreak'] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options['addDecl']) { + $atts = array(); + $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null; + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding) + . $this->options['linebreak'] + . $this->_serializedData; + } + + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return true; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData === null) { + return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @access private + * @param mixed $value + * @param string $tagName + * @param array $attributes + * @return string + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @access private + * @param array $array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * @return string $string serialized data + * @uses XML_Util::isValidName() to check, whether key has to be substituted + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + + /** + * check for special attributes + */ + if ($this->options['attributesArray'] !== null) { + if (isset($array[$this->options['attributesArray']])) { + $attributes = $array[$this->options['attributesArray']]; + unset($array[$this->options['attributesArray']]); + } + /** + * check for special content + */ + if ($this->options['contentName'] !== null) { + if (isset($array[$this->options['contentName']])) { + $_content = $array[$this->options['contentName']]; + unset($array[$this->options['contentName']]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && $this->options['mode'] == 'simplexml') { + $indexed = true; + if (!count($array)) { + $indexed = false; + } + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed && $this->options['mode'] == 'simplexml') { + $string = ''; + foreach ($array as $key => $val) { + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($val['attribs'])) { + if ($val['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + if ($this->_curdir == '/') { + $this->_curdir = ''; + } + $this->_curdir .= '/' . $val['attribs']['name']; + } + } + } + $string .= $this->_serializeValue( $val, $tagName, $attributes); + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + $string .= ' <!-- ' . $this->_curdir . ' -->'; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + + $string .= $this->options['linebreak']; + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $string .= str_repeat($this->options['indent'], $this->_tagDepth); + } + } + return rtrim($string); + } + } + + if ($this->options['scalarAsAttributes'] === true) { + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options['prependAttributes'].$key] = $value; + } + } + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + + } else { + $this->_tagDepth++; + $tmp = $this->options['linebreak']; + foreach ($array as $key => $value) { + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + // copy key + $origKey = $key; + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + if (PEAR::isError($valid)) { + if ($this->options['classAsTagName'] && is_object($value)) { + $key = get_class($value); + } else { + $key = $this->options['defaultTagName']; + } + } + $atts = array(); + if ($this->options['typeHints'] === true) { + $atts[$this->options['typeAttribute']] = gettype($value); + if ($key !== $origKey) { + $atts[$this->options['keyAttribute']] = (string)$origKey; + } + + } + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($value['attribs'])) { + if ($value['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + $this->_curdir .= '/' . $value['attribs']['name']; + } + } + } + + if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) { + $value .= str_repeat($this->options['indent'], $this->_tagDepth); + } + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value ) + ); + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (isset($value['attribs'])) { + $tmp .= ' <!-- ' . $this->_curdir . ' -->'; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + } + $tmp .= $this->options['linebreak']; + } + + $this->_tagDepth--; + if ($this->options['indent']!==null && $this->_tagDepth>0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options['typeHints'] === true) { + if (!isset($tag['attributes'][$this->options['typeAttribute']])) { + $tag['attributes'][$this->options['typeAttribute']] = 'array'; + } + } + + $string = $this->_createXMLTag($tag, false); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @access private + * @param array $tag tag definition + * @param boolean $replaceEntities whether to replace XML entities in content or not + * @return string $string XML tag + */ + function _createXMLTag($tag, $replaceEntities = true) + { + if ($this->options['indentAttributes'] !== false) { + $multiline = true; + $indent = str_repeat($this->options['indent'], $this->_tagDepth); + + if ($this->options['indentAttributes'] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options['indentAttributes']; + } + } else { + $indent = $multiline = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } + } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options['encoding'] == 'UTF-8' && + version_compare(phpversion(), '5.0.0', 'lt') + ) { + $tag['content'] = utf8_encode($tag['content']); + } + + if ($replaceEntities === true) { + $replaceEntities = XML_UTIL_ENTITIES_XML; + } + + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/PackageFile/Parser/v1.php b/includes/pear/PEAR/PackageFile/Parser/v1.php new file mode 100644 index 0000000..bbf5989 --- /dev/null +++ b/includes/pear/PEAR/PackageFile/Parser/v1.php @@ -0,0 +1,459 @@ +<?php +/** + * package.xml parsing class, package.xml version 1.0 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * package.xml abstraction class + */ +require_once 'PEAR/PackageFile/v1.php'; +/** + * Parser for package.xml version 1.0 + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v1 +{ + var $_registry; + var $_config; + var $_logger; + /** + * BC hack to allow PEAR_Common::infoFromString() to sort of + * work with the version 2.0 format - there's no filelist though + * @param PEAR_PackageFile_v2 + */ + function fromV2($packagefile) + { + $info = $packagefile->getArray(true); + $ret = new PEAR_PackageFile_v1; + $ret->fromArray($info['old']); + } + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * @param string contents of package.xml file, version 1.0 + * @return bool success of parsing + */ + function &parse($data, $file, $archive = false) + { + if (!extension_loaded('xml')) { + return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension'); + } + $xp = xml_parser_create(); + if (!$xp) { + $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml'); + return $a; + } + xml_set_object($xp, $this); + xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0'); + xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0'); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); + + $this->element_stack = array(); + $this->_packageInfo = array('provides' => array()); + $this->current_element = false; + unset($this->dir_install); + $this->_packageInfo['filelist'] = array(); + $this->filelist =& $this->_packageInfo['filelist']; + $this->dir_names = array(); + $this->in_changelog = false; + $this->d_i = 0; + $this->cdata = ''; + $this->_isValid = true; + + if (!xml_parse($xp, $data, 1)) { + $code = xml_get_error_code($xp); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + $a = &PEAR::raiseError(sprintf("XML error: %s at line %d", + $str = xml_error_string($code), $line), 2); + return $a; + } + + xml_parser_free($xp); + + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + if (isset($this->_logger)) { + $pf->setLogger($this->_logger); + } + $pf->setPackagefile($file, $archive); + $pf->fromArray($this->_packageInfo); + return $pf; + } + // {{{ _unIndent() + + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } elseif (trim(substr($line, 0, $indent_len))) { + $data .= ltrim($line); + } + } + return $data; + } + + // Support for package DTD v1.0: + // {{{ _element_start_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_start_1_0($xp, $name, $attribs) + { + array_push($this->element_stack, $name); + $this->current_element = $name; + $spos = sizeof($this->element_stack) - 2; + $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : ''; + $this->current_attributes = $attribs; + $this->cdata = ''; + switch ($name) { + case 'dir': + if ($this->in_changelog) { + break; + } + if (array_key_exists('name', $attribs) && $attribs['name'] != '/') { + $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) { + $attribs['name'] = substr($attribs['name'], 0, + strlen($attribs['name']) - 1); + } + if (strpos($attribs['name'], '/') === 0) { + $attribs['name'] = substr($attribs['name'], 1); + } + $this->dir_names[] = $attribs['name']; + } + if (isset($attribs['baseinstalldir'])) { + $this->dir_install = $attribs['baseinstalldir']; + } + if (isset($attribs['role'])) { + $this->dir_role = $attribs['role']; + } + break; + case 'file': + if ($this->in_changelog) { + break; + } + if (isset($attribs['name'])) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + unset($attribs['name']); + $this->current_path = $path; + $this->filelist[$path] = $attribs; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'replace': + if (!$this->in_changelog) { + $this->filelist[$this->current_path]['replacements'][] = $attribs; + } + break; + case 'maintainers': + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; // maintainers array index + break; + case 'maintainer': + // compatibility check + if (!isset($this->_packageInfo['maintainers'])) { + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; + } + $this->_packageInfo['maintainers'][$this->m_i] = array(); + $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i]; + break; + case 'changelog': + $this->_packageInfo['changelog'] = array(); + $this->c_i = 0; // changelog array index + $this->in_changelog = true; + break; + case 'release': + if ($this->in_changelog) { + $this->_packageInfo['changelog'][$this->c_i] = array(); + $this->current_release = &$this->_packageInfo['changelog'][$this->c_i]; + } else { + $this->current_release = &$this->_packageInfo; + } + break; + case 'deps': + if (!$this->in_changelog) { + $this->_packageInfo['release_deps'] = array(); + } + break; + case 'dep': + // dependencies array index + if (!$this->in_changelog) { + $this->d_i++; + isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false; + $this->_packageInfo['release_deps'][$this->d_i] = $attribs; + } + break; + case 'configureoptions': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'] = array(); + } + break; + case 'configureoption': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'][] = $attribs; + } + break; + case 'provides': + if (empty($attribs['type']) || empty($attribs['name'])) { + break; + } + $attribs['explicit'] = true; + $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs; + break; + case 'package' : + if (isset($attribs['version'])) { + $this->_packageInfo['xsdversion'] = trim($attribs['version']); + } else { + $this->_packageInfo['xsdversion'] = '1.0'; + } + if (isset($attribs['packagerversion'])) { + $this->_packageInfo['packagerversion'] = $attribs['packagerversion']; + } + break; + } + } + + // }}} + // {{{ _element_end_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_end_1_0($xp, $name) + { + $data = trim($this->cdata); + switch ($name) { + case 'name': + switch ($this->prev_element) { + case 'package': + $this->_packageInfo['package'] = $data; + break; + case 'maintainer': + $this->current_maintainer['name'] = $data; + break; + } + break; + case 'extends' : + $this->_packageInfo['extends'] = $data; + break; + case 'summary': + $this->_packageInfo['summary'] = $data; + break; + case 'description': + $data = $this->_unIndent($this->cdata); + $this->_packageInfo['description'] = $data; + break; + case 'user': + $this->current_maintainer['handle'] = $data; + break; + case 'email': + $this->current_maintainer['email'] = $data; + break; + case 'role': + $this->current_maintainer['role'] = $data; + break; + case 'version': + if ($this->in_changelog) { + $this->current_release['version'] = $data; + } else { + $this->_packageInfo['version'] = $data; + } + break; + case 'date': + if ($this->in_changelog) { + $this->current_release['release_date'] = $data; + } else { + $this->_packageInfo['release_date'] = $data; + } + break; + case 'notes': + // try to "de-indent" release notes in case someone + // has been over-indenting their xml ;-) + // Trim only on the right side + $data = rtrim($this->_unIndent($this->cdata)); + if ($this->in_changelog) { + $this->current_release['release_notes'] = $data; + } else { + $this->_packageInfo['release_notes'] = $data; + } + break; + case 'warnings': + if ($this->in_changelog) { + $this->current_release['release_warnings'] = $data; + } else { + $this->_packageInfo['release_warnings'] = $data; + } + break; + case 'state': + if ($this->in_changelog) { + $this->current_release['release_state'] = $data; + } else { + $this->_packageInfo['release_state'] = $data; + } + break; + case 'license': + if ($this->in_changelog) { + $this->current_release['release_license'] = $data; + } else { + $this->_packageInfo['release_license'] = $data; + } + break; + case 'dep': + if ($data && !$this->in_changelog) { + $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data; + } + break; + case 'dir': + if ($this->in_changelog) { + break; + } + array_pop($this->dir_names); + break; + case 'file': + if ($this->in_changelog) { + break; + } + if ($data) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= $data; + $this->filelist[$path] = $this->current_attributes; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'maintainer': + if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) { + $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead'; + } + $this->m_i++; + break; + case 'release': + if ($this->in_changelog) { + $this->c_i++; + } + break; + case 'changelog': + $this->in_changelog = false; + break; + } + array_pop($this->element_stack); + $spos = sizeof($this->element_stack) - 1; + $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : ''; + $this->cdata = ''; + } + + // }}} + // {{{ _pkginfo_cdata_1_0() + + /** + * XML parser callback for character data. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name character data + * + * @return void + * + * @access private + */ + function _pkginfo_cdata_1_0($xp, $data) + { + if (isset($this->cdata)) { + $this->cdata .= $data; + } + } + + // }}} +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/PackageFile/Parser/v2.php b/includes/pear/PEAR/PackageFile/Parser/v2.php new file mode 100644 index 0000000..cb81041 --- /dev/null +++ b/includes/pear/PEAR/PackageFile/Parser/v2.php @@ -0,0 +1,113 @@ +<?php +/** + * package.xml parsing class, package.xml version 2.0 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * Parser for package.xml version 2.0 + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v2 extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } else { + $data .= $line . "\n"; + } + } + return $data; + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + if ($element == 'notes') { + return trim($this->_unIndent($data)); + } + return trim($data); + } + + /** + * @param string + * @param string file name of the package.xml + * @param string|false name of the archive this package.xml came from, if any + * @param string class name to instantiate and return. This must be PEAR_PackageFile_v2 or + * a subclass + * @return PEAR_PackageFile_v2 + */ + function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2') + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new $class; + $ret->encoding = $this->encoding; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + $ret->setPackagefile($file, $archive); + return $ret; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/PackageFile/v1.php b/includes/pear/PEAR/PackageFile/v1.php new file mode 100644 index 0000000..2884cc4 --- /dev/null +++ b/includes/pear/PEAR/PackageFile/v1.php @@ -0,0 +1,1612 @@ +<?php +/** + * PEAR_PackageFile_v1, package.xml version 1.0 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_PACKAGEFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_PACKAGEFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_PACKAGEFILE_ERROR_PARSER_ERROR', 5); + +/** + * Error code used when there is no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NAME', 6); + +/** + * Error code when a package name is not valid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_NAME', 7); + +/** + * Error code used when no summary is parsed + */ +define('PEAR_PACKAGEFILE_ERROR_NO_SUMMARY', 8); + +/** + * Error code for summaries that are more than 1 line + */ +define('PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY', 9); + +/** + * Error code used when no description is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION', 10); + +/** + * Error code used when no license is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_LICENSE', 11); + +/** + * Error code used when a <version> version number is not present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12); + +/** + * Error code used when a <version> version number is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_VERSION', 13); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_STATE', 14); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_STATE', 15); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DATE', 16); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DATE', 17); + +/** + * Error code when no release notes are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NOTES', 18); + +/** + * Error code when no maintainers are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS', 19); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE', 20); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE', 21); + +/** + * Error code when a maintainer has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME', 22); + +/** + * Error code when a maintainer has no email + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL', 23); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_MAINTROLE', 24); + +/** + * Error code when a dependency is not a PHP dependency, but has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPNAME', 25); + +/** + * Error code when a dependency has no type (pkg, php, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE', 26); + +/** + * Error code when a dependency has no relation (lt, ge, has, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPREL', 27); + +/** + * Error code when a dependency is not a 'has' relation, but has no version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION', 28); + +/** + * Error code when a dependency has an invalid relation + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPREL', 29); + +/** + * Error code when a dependency has an invalid type + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPTYPE', 30); + +/** + * Error code when a dependency has an invalid optional option + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL', 31); + +/** + * Error code when a dependency is a pkg dependency, and has an invalid package name + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPNAME', 32); + +/** + * Error code when a dependency has a channel="foo" attribute, and foo is not a registered channel + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_DEPCHANNEL', 33); + +/** + * Error code when rel="has" and version attribute is present. + */ +define('PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED', 34); + +/** + * Error code when type="php" and dependency name is present + */ +define('PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED', 35); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFNAME', 36); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT', 37); + +/** + * Error code when a file in the filelist has an invalid role + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE', 38); + +/** + * Error code when a file in the filelist has no role + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILEROLE', 39); + +/** + * Error code when analyzing a php source file that has parse errors + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE', 40); + +/** + * Error code when analyzing a php source file reveals a source element + * without a package name prefix + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX', 41); + +/** + * Error code when an unknown channel is specified + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_CHANNEL', 42); + +/** + * Error code when no files are found in the filelist + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILES', 43); + +/** + * Error code when a file is not valid php according to _analyzeSourceCode() + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILE', 44); + +/** + * Error code when the channel validator returns an error or warning + */ +define('PEAR_PACKAGEFILE_ERROR_CHANNELVAL', 45); + +/** + * Error code when a php5 package is packaged in php4 (analysis doesn't work) + */ +define('PEAR_PACKAGEFILE_ERROR_PHP5', 46); + +/** + * Error code when a file is listed in package.xml but does not exist + */ +define('PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND', 47); + +/** + * Error code when a <dep type="php" rel="not"... is encountered (use rel="ne") + */ +define('PEAR_PACKAGEFILE_PHP_NO_NOT', 48); + +/** + * Error code when a package.xml contains non-ISO-8859-1 characters + */ +define('PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS', 49); + +/** + * Error code when a dependency is not a 'has' relation, but has no version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION', 50); + +/** + * Error code when a package has no lead developer + */ +define('PEAR_PACKAGEFILE_ERROR_NO_LEAD', 51); + +/** + * Error code when a filename begins with "." + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME', 52); +/** + * package.xml encapsulator + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v1 +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * A registry object, used to access the package name validation regex for non-standard channels + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * An object that contains a log method that matches PEAR_Common::log's signature + * @var object + * @access private + */ + var $_logger; + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo; + + /** + * path to package.xml + * @var string + * @access private + */ + var $_packageFile; + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string + * @access private + */ + var $_archiveFile; + + /** + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack + * @param string Name of Error Stack class to use. + */ + function PEAR_PackageFile_v1() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = 0; + } + + function installBinary($installer) + { + return false; + } + + function isExtension($name) + { + return false; + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setRequestedGroup() + { + // placeholder + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + function getInstalledBinary() + { + return false; + } + + function listPostinstallScripts() + { + return false; + } + + function initPostinstallScripts() + { + return false; + } + + function setLogger(&$logger) + { + if ($logger && (!is_object($logger) || !method_exists($logger, 'log'))) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + function getPackageFile() + { + return isset($this->_packageFile) ? $this->_packageFile : false; + } + + function getPackageType() + { + return 'php'; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + function packageInfo($field) + { + if (!is_string($field) || empty($field) || + !isset($this->_packageInfo[$field])) { + return false; + } + return $this->_packageInfo[$field]; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + function fromArray($pinfo) + { + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + function getChannel() + { + return 'pear.php.net'; + } + + function getUri() + { + return false; + } + + function getTime() + { + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + /** + * @return array + */ + function toArray() + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray(); + } + + function getArray() + { + return $this->_packageInfo; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['package'])) { + return $this->_packageInfo['package']; + } + return false; + } + + /** + * WARNING - don't use this unless you know what you are doing + */ + function setRawPackage($package) + { + $this->_packageInfo['package'] = $package; + } + + function setPackage($package) + { + $this->_packageInfo['package'] = $package; + $this->_isValid = false; + } + + function getVersion() + { + if (isset($this->_packageInfo['version'])) { + return $this->_packageInfo['version']; + } + return false; + } + + function setVersion($version) + { + $this->_packageInfo['version'] = $version; + $this->_isValid = false; + } + + function clearMaintainers() + { + unset($this->_packageInfo['maintainers']); + } + + function getMaintainers() + { + if (isset($this->_packageInfo['maintainers'])) { + return $this->_packageInfo['maintainers']; + } + return false; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email) + { + $this->_packageInfo['maintainers'][] = + array('handle' => $handle, 'role' => $role, 'email' => $email, 'name' => $name); + $this->_isValid = false; + } + + function updateMaintainer($role, $handle, $name, $email) + { + $found = false; + if (!isset($this->_packageInfo['maintainers']) || + !is_array($this->_packageInfo['maintainers'])) { + return $this->addMaintainer($role, $handle, $name, $email); + } + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + } + $this->addMaintainer($role, $handle, $name, $email); + } + + function deleteMaintainer($handle) + { + $found = false; + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + return true; + } + return false; + } + + function getState() + { + if (isset($this->_packageInfo['release_state'])) { + return $this->_packageInfo['release_state']; + } + return false; + } + + function setRawState($state) + { + $this->_packageInfo['release_state'] = $state; + } + + function setState($state) + { + $this->_packageInfo['release_state'] = $state; + $this->_isValid = false; + } + + function getDate() + { + if (isset($this->_packageInfo['release_date'])) { + return $this->_packageInfo['release_date']; + } + return false; + } + + function setDate($date) + { + $this->_packageInfo['release_date'] = $date; + $this->_isValid = false; + } + + function getLicense() + { + if (isset($this->_packageInfo['release_license'])) { + return $this->_packageInfo['release_license']; + } + return false; + } + + function setLicense($date) + { + $this->_packageInfo['release_license'] = $date; + $this->_isValid = false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function setSummary($summary) + { + $this->_packageInfo['summary'] = $summary; + $this->_isValid = false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function setDescription($desc) + { + $this->_packageInfo['description'] = $desc; + $this->_isValid = false; + } + + function getNotes() + { + if (isset($this->_packageInfo['release_notes'])) { + return $this->_packageInfo['release_notes']; + } + return false; + } + + function setNotes($notes) + { + $this->_packageInfo['release_notes'] = $notes; + $this->_isValid = false; + } + + function getDeps() + { + if (isset($this->_packageInfo['release_deps'])) { + return $this->_packageInfo['release_deps']; + } + return false; + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + unset($this->_packageInfo['release_deps']); + } + + function addPhpDep($version, $rel) + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'php', + 'rel' => $rel, + 'version' => $version); + } + + function addPackageDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $dep = + array('type' => 'pkg', + 'name' => $name, + 'rel' => $rel, + 'optional' => $optional); + if ($rel != 'has' && $rel != 'not') { + $dep['version'] = $version; + } + $this->_packageInfo['release_deps'][] = $dep; + } + + function addExtensionDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'ext', + 'name' => $name, + 'rel' => $rel, + 'version' => $version, + 'optional' => $optional); + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['release_deps'] = $deps; + } + + function hasDeps() + { + return isset($this->_packageInfo['release_deps']) && + count($this->_packageInfo['release_deps']); + } + + function getDependencyGroup($group) + { + return false; + } + + function isCompatible($pf) + { + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + function isSubpackage($p) + { + return false; + } + + function dependsOn($package, $channel) + { + if (strtolower($channel) != 'pear.php.net') { + return false; + } + if (!($deps = $this->getDeps())) { + return false; + } + foreach ($deps as $dep) { + if ($dep['type'] != 'pkg') { + continue; + } + if (strtolower($dep['name']) == strtolower($package)) { + return true; + } + } + return false; + } + + function getConfigureOptions() + { + if (isset($this->_packageInfo['configure_options'])) { + return $this->_packageInfo['configure_options']; + } + return false; + } + + function hasConfigureOptions() + { + return isset($this->_packageInfo['configure_options']) && + count($this->_packageInfo['configure_options']); + } + + function addConfigureOption($name, $prompt, $default = false) + { + $o = array('name' => $name, 'prompt' => $prompt); + if ($default !== false) { + $o['default'] = $default; + } + if (!isset($this->_packageInfo['configure_options'])) { + $this->_packageInfo['configure_options'] = array(); + } + $this->_packageInfo['configure_options'][] = $o; + } + + function clearConfigureOptions() + { + unset($this->_packageInfo['configure_options']); + } + + function getProvides() + { + if (isset($this->_packageInfo['provides'])) { + return $this->_packageInfo['provides']; + } + return false; + } + + function getProvidesExtension() + { + return false; + } + + function addFile($dir, $file, $attrs) + { + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $file = $dir . $file; + $file = preg_replace('![\\/]+!', '/', $file); + $this->_packageInfo['filelist'][$file] = $attrs; + } + + function getInstallationFilelist() + { + return $this->getFilelist(); + } + + function getFilelist() + { + if (isset($this->_packageInfo['filelist'])) { + return $this->_packageInfo['filelist']; + } + return false; + } + + function setFileAttribute($file, $attr, $value) + { + $this->_packageInfo['filelist'][$file][$attr] = $value; + } + + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts); + } else { + $this->_packageInfo['filelist'][$file] = $atts; + } + } + + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function getPackagexmlVersion() + { + return '1.0'; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + // }}} + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params, false, false, debug_backtrace()); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params, false, false, debug_backtrace()); + } + + /** + * @param integer error code + * @access protected + */ + function _getErrorMessage() + { + return array( + PEAR_PACKAGEFILE_ERROR_NO_NAME => + 'Missing Package Name', + PEAR_PACKAGEFILE_ERROR_NO_SUMMARY => + 'No summary found', + PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY => + 'Summary should be on one line', + PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION => + 'Missing description', + PEAR_PACKAGEFILE_ERROR_NO_LICENSE => + 'Missing license', + PEAR_PACKAGEFILE_ERROR_NO_VERSION => + 'No release version found', + PEAR_PACKAGEFILE_ERROR_NO_STATE => + 'No release state found', + PEAR_PACKAGEFILE_ERROR_NO_DATE => + 'No release date found', + PEAR_PACKAGEFILE_ERROR_NO_NOTES => + 'No release notes found', + PEAR_PACKAGEFILE_ERROR_NO_LEAD => + 'Package must have at least one lead maintainer', + PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS => + 'No maintainers found, at least one must be defined', + PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE => + 'Maintainer %index% has no handle (user ID at channel server)', + PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE => + 'Maintainer %index% has no role', + PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME => + 'Maintainer %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL => + 'Maintainer %index% has no email', + PEAR_PACKAGEFILE_ERROR_NO_DEPNAME => + 'Dependency %index% is not a php dependency, and has no name', + PEAR_PACKAGEFILE_ERROR_NO_DEPREL => + 'Dependency %index% has no relation (rel)', + PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE => + 'Dependency %index% has no type', + PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED => + 'PHP Dependency %index% has a name attribute of "%name%" which will be' . + ' ignored!', + PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION => + 'Dependency %index% is not a rel="has" or rel="not" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION => + 'Dependency %index% is a type="php" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED => + 'Dependency %index% is a rel="%rel%" dependency, versioning is ignored', + PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL => + 'Dependency %index% has invalid optional value "%opt%", should be yes or no', + PEAR_PACKAGEFILE_PHP_NO_NOT => + 'Dependency %index%: php dependencies cannot use "not" rel, use "ne"' . + ' to exclude specific versions', + PEAR_PACKAGEFILE_ERROR_NO_CONFNAME => + 'Configure Option %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT => + 'Configure Option %index% has no prompt', + PEAR_PACKAGEFILE_ERROR_NO_FILES => + 'No files in <filelist> section of package.xml', + PEAR_PACKAGEFILE_ERROR_NO_FILEROLE => + 'File "%file%" has no role, expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE => + 'File "%file%" has invalid role "%role%", expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME => + 'File "%file%" cannot start with ".", cannot package or install', + PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE => + 'Parser error: invalid PHP found in file "%file%"', + PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX => + 'in %file%: %type% "%name%" not prefixed with package name "%package%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILE => + 'Parser error: invalid PHP file "%file%"', + PEAR_PACKAGEFILE_ERROR_CHANNELVAL => + 'Channel validator error: field "%field%" - %reason%', + PEAR_PACKAGEFILE_ERROR_PHP5 => + 'Error, PHP5 token encountered in %file%, analysis should be in PHP5', + PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND => + 'File "%file%" in package.xml does not exist', + PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS => + 'Package.xml contains non-ISO-8859-1 characters, and may not validate', + ); + } + + /** + * Validate XML package definition file. + * + * @access public + * @return boolean + */ + function validate($state = PEAR_VALIDATE_NORMAL, $nofilechecking = false) + { + if (($this->_isValid & $state) == $state) { + return true; + } + $this->_isValid = true; + $info = $this->_packageInfo; + if (empty($info['package'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NAME); + $this->_packageName = $pn = 'unknown'; + } else { + $this->_packageName = $pn = $info['package']; + } + + if (empty($info['summary'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (empty($info['description'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION); + } + if (empty($info['release_license'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LICENSE); + } + if (empty($info['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_VERSION); + } + if (empty($info['release_state'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_STATE); + } + if (empty($info['release_date'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DATE); + } + if (empty($info['release_notes'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NOTES); + } + if (empty($info['maintainers'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS); + } else { + $haslead = false; + $i = 1; + foreach ($info['maintainers'] as $m) { + if (empty($m['handle'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE, + array('index' => $i)); + } + if (empty($m['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE, + array('index' => $i, 'roles' => PEAR_Common::getUserRoles())); + } elseif ($m['role'] == 'lead') { + $haslead = true; + } + if (empty($m['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME, + array('index' => $i)); + } + if (empty($m['email'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL, + array('index' => $i)); + } + $i++; + } + if (!$haslead) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LEAD); + } + } + if (!empty($info['release_deps'])) { + $i = 1; + foreach ($info['release_deps'] as $d) { + if (!isset($d['type']) || empty($d['type'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE, + array('index' => $i, 'types' => PEAR_Common::getDependencyTypes())); + continue; + } + if (!isset($d['rel']) || empty($d['rel'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPREL, + array('index' => $i, 'rels' => PEAR_Common::getDependencyRelations())); + continue; + } + if (!empty($d['optional'])) { + if (!in_array($d['optional'], array('yes', 'no'))) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL, + array('index' => $i, 'opt' => $d['optional'])); + } + } + if ($d['rel'] != 'has' && $d['rel'] != 'not' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION, + array('index' => $i)); + } elseif (($d['rel'] == 'has' || $d['rel'] == 'not') && !empty($d['version'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED, + array('index' => $i, 'rel' => $d['rel'])); + } + if ($d['type'] == 'php' && !empty($d['name'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED, + array('index' => $i, 'name' => $d['name'])); + } elseif ($d['type'] != 'php' && empty($d['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPNAME, + array('index' => $i)); + } + if ($d['type'] == 'php' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION, + array('index' => $i)); + } + if (($d['rel'] == 'not') && ($d['type'] == 'php')) { + $this->_validateError(PEAR_PACKAGEFILE_PHP_NO_NOT, + array('index' => $i)); + } + $i++; + } + } + if (!empty($info['configure_options'])) { + $i = 1; + foreach ($info['configure_options'] as $c) { + if (empty($c['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFNAME, + array('index' => $i)); + } + if (empty($c['prompt'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT, + array('index' => $i)); + } + $i++; + } + } + if (empty($info['filelist'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILES); + $errors[] = 'no files'; + } else { + foreach ($info['filelist'] as $file => $fa) { + if (empty($fa['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILEROLE, + array('file' => $file, 'roles' => PEAR_Common::getFileRoles())); + continue; + } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE, + array('file' => $file, 'role' => $fa['role'], 'roles' => PEAR_Common::getFileRoles())); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file))) { + // file contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file)); + } + if (isset($fa['install-as']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['install-as']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [installed as ' . $fa['install-as'] . ']')); + } + if (isset($fa['baseinstalldir']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['baseinstalldir']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [baseinstalldir ' . $fa['baseinstalldir'] . ']')); + } + } + } + if (isset($this->_registry) && $this->_isValid) { + $chan = $this->_registry->getChannel('pear.php.net'); + if (PEAR::isError($chan)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $chan->getMessage()); + return $this->_isValid = 0; + } + $validator = $chan->getValidationObject(); + $validator->setPackageFile($this); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $error); + } + foreach ($failures['warnings'] as $warning) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $warning); + } + } + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$nofilechecking) { + if ($this->_analyzePhpFiles()) { + $this->_isValid = true; + } + } + if ($this->_isValid) { + return $this->_isValid = $state; + } + return $this->_isValid = 0; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_logger) ? array(&$this->_logger, 'log') : + array($common, 'log'); + $info = $this->getFilelist(); + foreach ($info as $file => $fa) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND, + array('file' => realpath($dir_prefix) . DIRECTORY_SEPARATOR . $file)); + continue; + } + if ($fa['role'] == 'php' && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->_analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $this->_buildProvidesArray($srcinfo); + } + } + } + $this->_packageName = $pn = $this->getPackage(); + $pnl = strlen($pn); + if (isset($this->_packageInfo['provides'])) { + foreach ((array) $this->_packageInfo['provides'] as $key => $what) { + if (isset($what['explicit'])) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } + } + } + return $this->_isValid; + } + + /** + * Get the default xml generator object + * + * @return PEAR_PackageFile_Generator_v1 + */ + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v1')) { + require_once 'PEAR/PackageFile/Generator/v1.php'; + } + $a = &new PEAR_PackageFile_Generator_v1($this); + return $a; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + // {{{ analyzeSourceCode() + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access private + */ + function _analyzeSourceCode($file) + { + if (!function_exists("token_get_all")) { + return false; + } + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + $tokens = token_get_all($contents); +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + )) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5, + array($file)); + } + } + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + continue 2; + } + } + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return false; + } + $file = basename($srcinfo['source_file']); + $pn = $this->getPackage(); + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->_packageInfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->_packageInfo['provides'][$key])) { + continue; + } + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + // }}} +} +?> diff --git a/includes/pear/PEAR/PackageFile/v2.php b/includes/pear/PEAR/PackageFile/v2.php new file mode 100644 index 0000000..27310d4 --- /dev/null +++ b/includes/pear/PEAR/PackageFile/v2.php @@ -0,0 +1,2049 @@ +<?php +/** + * PEAR_PackageFile_v2, package.xml version 2.0 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v2 +{ + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo = array(); + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string|false + * @access private + */ + var $_archiveFile; + + /** + * path to package .xml or false if this is an abstract parsed-from-string xml + * @var string|false + * @access private + */ + var $_packageFile; + + /** + * This is used by file analysis routines to log progress information + * @var PEAR_Common + * @access protected + */ + var $_logger; + + /** + * This is set to the highest validation level that has been validated + * + * If the package.xml is invalid or unknown, this is set to 0. If + * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL. If + * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING + * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING. This allows validation + * "caching" to occur, which is particularly important for package validation, so + * that PHP files are not validated twice + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * True if the filelist has been validated + * @param bool + */ + var $_filesValid = false; + + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * @var PEAR_Config + * @access protected + */ + var $_config; + + /** + * Optional Dependency group requested for installation + * @var string + * @access private + */ + var $_requestedGroup = false; + + /** + * @var PEAR_ErrorStack + * @access protected + */ + var $_stack; + + /** + * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible + */ + var $_tasksNs; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @var PEAR_PackageFile_v2_Validator + */ + var $_v2Validator; + + /** + * The constructor merely sets up the private error stack + */ + function PEAR_PackageFile_v2() + { + $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null); + $this->_isValid = false; + } + + /** + * To make unit-testing easier + * @param PEAR_Frontend_* + * @param array options + * @param PEAR_Config + * @return PEAR_Downloader + * @access protected + */ + function &getPEARDownloader(&$i, $o, &$c) + { + $z = &new PEAR_Downloader($i, $o, $c); + return $z; + } + + /** + * To make unit-testing easier + * @param PEAR_Config + * @param array options + * @param array package name as returned from {@link PEAR_Registry::parsePackageName()} + * @param int PEAR_VALIDATE_* constant + * @return PEAR_Dependency2 + * @access protected + */ + function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $o, $p, $s); + return $z; + } + + function getInstalledBinary() + { + return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] : + false; + } + + /** + * Installation of source package has failed, attempt to download and install the + * binary version of this package. + * @param PEAR_Installer + * @return array|false + */ + function installBinary(&$installer) + { + if (!OS_WINDOWS) { + $a = false; + return $a; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $releasetype = $this->getPackageType() . 'release'; + if (!is_array($installer->getInstallPackages())) { + $a = false; + return $a; + } + foreach ($installer->getInstallPackages() as $p) { + if ($p->isExtension($this->_packageInfo['providesextension'])) { + if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') { + $a = false; + return $a; // the user probably downloaded it separately + } + } + } + if (isset($this->_packageInfo[$releasetype]['binarypackage'])) { + $installer->log(0, 'Attempting to download binary version of extension "' . + $this->_packageInfo['providesextension'] . '"'); + $params = $this->_packageInfo[$releasetype]['binarypackage']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + if (isset($this->_packageInfo['channel'])) { + foreach ($params as $i => $param) { + $params[$i] = array('channel' => $this->_packageInfo['channel'], + 'package' => $param, 'version' => $this->getVersion()); + } + } + $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(), + $installer->config); + $verbose = $dl->config->get('verbose'); + $dl->config->set('verbose', -1); + foreach ($params as $param) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $dl->download(array($param)); + PEAR::popErrorHandling(); + if (is_array($ret) && count($ret)) { + break; + } + } + $dl->config->set('verbose', $verbose); + if (is_array($ret)) { + if (count($ret) == 1) { + $pf = $ret[0]->getPackageFile(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $installer->install($ret[0]); + PEAR::popErrorHandling(); + if (is_array($err)) { + $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage(); + // "install" self, so all dependencies will work transparently + $this->_registry->addPackage2($this); + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" successful'); + $a = array($ret[0], $err); + return $a; + } + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" failed'); + } + } + } + } + $a = false; + return $a; + } + + /** + * @return string|false Extension name + */ + function getProvidesExtension() + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (isset($this->_packageInfo['providesextension'])) { + return $this->_packageInfo['providesextension']; + } + } + return false; + } + + /** + * @param string Extension name + * @return bool + */ + function isExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + return $this->_packageInfo['providesextension'] == $extension; + } + return false; + } + + /** + * Tests whether every part of the package.xml 1.0 is represented in + * this package.xml 2.0 + * @param PEAR_PackageFile_v1 + * @return bool + */ + function isEquivalent($pf1) + { + if (!$pf1) { + return true; + } + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_stack->getErrors(true); + if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + $pass = true; + if ($pf1->getPackage() != $this->getPackage()) { + $this->_differentPackage($pf1->getPackage()); + $pass = false; + } + if ($pf1->getVersion() != $this->getVersion()) { + $this->_differentVersion($pf1->getVersion()); + $pass = false; + } + if (trim($pf1->getSummary()) != $this->getSummary()) { + $this->_differentSummary($pf1->getSummary()); + $pass = false; + } + if (preg_replace('/\s+/', '', $pf1->getDescription()) != + preg_replace('/\s+/', '', $this->getDescription())) { + $this->_differentDescription($pf1->getDescription()); + $pass = false; + } + if ($pf1->getState() != $this->getState()) { + $this->_differentState($pf1->getState()); + $pass = false; + } + if (!strstr(preg_replace('/\s+/', '', $this->getNotes()), + preg_replace('/\s+/', '', $pf1->getNotes()))) { + $this->_differentNotes($pf1->getNotes()); + $pass = false; + } + $mymaintainers = $this->getMaintainers(); + $yourmaintainers = $pf1->getMaintainers(); + for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) { + $reset = false; + for ($i2 = 0; $i2 < count($mymaintainers); $i2++) { + if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) { + if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) { + $this->_differentRole($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']); + $pass = false; + } + if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) { + $this->_differentEmail($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']); + $pass = false; + } + if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) { + $this->_differentName($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']); + $pass = false; + } + unset($mymaintainers[$i2]); + $mymaintainers = array_values($mymaintainers); + unset($yourmaintainers[$i1]); + $yourmaintainers = array_values($yourmaintainers); + $reset = true; + break; + } + } + if ($reset) { + $i1 = -1; + } + } + $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers); + $filelist = $this->getFilelist(); + foreach ($pf1->getFilelist() as $file => $atts) { + if (!isset($filelist[$file])) { + $this->_missingFile($file); + $pass = false; + } + } + return $pass; + } + + function _differentPackage($package) + { + $this->_stack->push(__FUNCTION__, 'error', array('package' => $package, + 'self' => $this->getPackage()), + 'package.xml 1.0 package "%package%" does not match "%self%"'); + } + + function _differentVersion($version) + { + $this->_stack->push(__FUNCTION__, 'error', array('version' => $version, + 'self' => $this->getVersion()), + 'package.xml 1.0 version "%version%" does not match "%self%"'); + } + + function _differentState($state) + { + $this->_stack->push(__FUNCTION__, 'error', array('state' => $state, + 'self' => $this->getState()), + 'package.xml 1.0 state "%state%" does not match "%self%"'); + } + + function _differentRole($handle, $role, $selfrole) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'role' => $role, 'self' => $selfrole), + 'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"'); + } + + function _differentEmail($handle, $email, $selfemail) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'email' => $email, 'self' => $selfemail), + 'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"'); + } + + function _differentName($handle, $name, $selfname) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'name' => $name, 'self' => $selfname), + 'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"'); + } + + function _unmatchedMaintainers($my, $yours) + { + if ($my) { + array_walk($my, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my), + 'package.xml 2.0 has unmatched extra maintainers "%handles%"'); + } + if ($yours) { + array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours), + 'package.xml 1.0 has unmatched extra maintainers "%handles%"'); + } + } + + function _differentNotes($notes) + { + $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...'; + $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() : + substr($this->getNotes(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes, + 'self' => $truncmynotes), + 'package.xml 1.0 release notes "%notes%" do not match "%self%"'); + } + + function _differentSummary($summary) + { + $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...'; + $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() : + substr($this->getsummary(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary, + 'self' => $truncmysummary), + 'package.xml 1.0 summary "%summary%" does not match "%self%"'); + } + + function _differentDescription($description) + { + $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...'); + $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() : + substr($this->getdescription(), 0, 24) . '...'); + $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription, + 'self' => $truncmydescription), + 'package.xml 1.0 description "%description%" does not match "%self%"'); + } + + function _missingFile($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'package.xml 1.0 file "%file%" is not present in <contents>'); + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawState($state) + { + if (!isset($this->_packageInfo['stability'])) { + $this->_packageInfo['stability'] = array(); + } + $this->_packageInfo['stability']['release'] = $state; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawCompatible($compatible) + { + $this->_packageInfo['compatible'] = $compatible; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawPackage($package) + { + $this->_packageInfo['name'] = $package; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawChannel($channel) + { + $this->_packageInfo['channel'] = $channel; + } + + function setRequestedGroup($group) + { + $this->_requestedGroup = $group; + } + + function getRequestedGroup() + { + if (isset($this->_requestedGroup)) { + return $this->_requestedGroup; + } + return false; + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + /** + * Determines whether this package.xml has post-install scripts or not + * @return array|false + */ + function listPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + $ret = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // ignored files will not be in the filelist + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $task = $this->getTask($tag); + $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL); + if ($task->isScript()) { + $ret[] = $filelist[$name]['installed_as']; + } + } + } + if (count($ret)) { + return $ret; + } + return false; + } + + /** + * Initialize post-install scripts for running + * + * This method can be used to detect post-install scripts, as the return value + * indicates whether any exist + * @return bool + */ + function initPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // file was not installed due to installconditions + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $taskname = $this->getTask($tag); + $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL); + if (!$task->isScript()) { + continue; // scripts are only handled after installation + } + $lastversion = isset($this->_packageInfo['_lastversion']) ? + $this->_packageInfo['_lastversion'] : null; + $task->init($raw, $atts, $lastversion); + $res = $task->startSession($this, $atts['installed_as']); + if (!$res) { + continue; // skip this file + } + if (PEAR::isError($res)) { + return $res; + } + $assign = &$task; + $this->_scripts[] = &$assign; + } + } + if (count($this->_scripts)) { + return true; + } + return false; + } + + function runPostinstallScripts() + { + if ($this->initPostinstallScripts()) { + $ui = &PEAR_Frontend::singleton(); + if ($ui) { + $ui->runPostinstallScripts($this->_scripts, $this); + } + } + } + + + /** + * Convert a recursive set of <dir> and <file> tags into a single <dir> tag with + * <file> tags. + */ + function flattenFilelist() + { + if (isset($this->_packageInfo['bundle'])) { + return; + } + $filelist = array(); + if (isset($this->_packageInfo['contents']['dir']['dir'])) { + $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']); + if (!isset($filelist[1])) { + $filelist = $filelist[0]; + } + $this->_packageInfo['contents']['dir']['file'] = $filelist; + unset($this->_packageInfo['contents']['dir']['dir']); + } else { + // else already flattened but check for baseinstalldir propagation + if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) { + if (isset($this->_packageInfo['contents']['dir']['file'][0])) { + foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) { + if (isset($file['attribs']['baseinstalldir'])) { + continue; + } + $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } else { + if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) { + $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } + } + } + } + + /** + * @param array the final flattened file list + * @param array the current directory being processed + * @param string|false any recursively inherited baeinstalldir attribute + * @param string private recursion variable + * @return array + * @access protected + */ + function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '') + { + if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) { + $baseinstall = $dir['attribs']['baseinstalldir']; + } + if (isset($dir['dir'])) { + if (!isset($dir['dir'][0])) { + $dir['dir'] = array($dir['dir']); + } + foreach ($dir['dir'] as $subdir) { + if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) { + $name = '*unknown*'; + } else { + $name = $subdir['attribs']['name']; + } + $newpath = empty($path) ? $name : + $path . '/' . $name; + $this->_getFlattenedFilelist($files, $subdir, + $baseinstall, $newpath); + } + } + if (isset($dir['file'])) { + if (!isset($dir['file'][0])) { + $dir['file'] = array($dir['file']); + } + foreach ($dir['file'] as $file) { + $attrs = $file['attribs']; + $name = $attrs['name']; + if ($baseinstall && !isset($attrs['baseinstalldir'])) { + $attrs['baseinstalldir'] = $baseinstall; + } + $attrs['name'] = empty($path) ? $name : $path . '/' . $name; + $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attrs['name']); + $file['attribs'] = $attrs; + $files[] = $file; + } + } + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setLogger(&$logger) + { + if (!is_object($logger) || !method_exists($logger, 'log')) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['dependencies'] = $deps; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setCompatible($compat) + { + $this->_packageInfo['compatible'] = $compat; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + function getPackageFile() + { + return $this->_packageFile; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + + /** + * Directly set the array that defines this packagefile + * + * WARNING: no validation. This should only be performed by internal methods + * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2 + * @param array + */ + function fromArray($pinfo) + { + unset($pinfo['old']); + unset($pinfo['xsdversion']); + // If the changelog isn't an array then it was passed in as an empty tag + if (isset($pinfo['changelog']) && !is_array($pinfo['changelog'])) { + unset($pinfo['changelog']); + } + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + /** + * @return array + */ + function toArray($forreg = false) + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray($forreg); + } + + function getArray($forReg = false) + { + if ($forReg) { + $arr = $this->_packageInfo; + $arr['old'] = array(); + $arr['old']['version'] = $this->getVersion(); + $arr['old']['release_date'] = $this->getDate(); + $arr['old']['release_state'] = $this->getState(); + $arr['old']['release_license'] = $this->getLicense(); + $arr['old']['release_notes'] = $this->getNotes(); + $arr['old']['release_deps'] = $this->getDeps(); + $arr['old']['maintainers'] = $this->getMaintainers(); + $arr['xsdversion'] = '2.0'; + return $arr; + } else { + $info = $this->_packageInfo; + unset($info['dirtree']); + if (isset($info['_lastversion'])) { + unset($info['_lastversion']); + } + if (isset($info['#binarypackage'])) { + unset($info['#binarypackage']); + } + return $info; + } + } + + function packageInfo($field) + { + $arr = $this->getArray(true); + if ($field == 'state') { + return $arr['stability']['release']; + } + if ($field == 'api-version') { + return $arr['version']['api']; + } + if ($field == 'api-state') { + return $arr['stability']['api']; + } + if (isset($arr['old'][$field])) { + if (!is_string($arr['old'][$field])) { + return null; + } + return $arr['old'][$field]; + } + if (isset($arr[$field])) { + if (!is_string($arr[$field])) { + return null; + } + return $arr[$field]; + } + return null; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['name'])) { + return $this->_packageInfo['name']; + } + return false; + } + + function getChannel() + { + if (isset($this->_packageInfo['uri'])) { + return '__uri'; + } + if (isset($this->_packageInfo['channel'])) { + return strtolower($this->_packageInfo['channel']); + } + return false; + } + + function getUri() + { + if (isset($this->_packageInfo['uri'])) { + return $this->_packageInfo['uri']; + } + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function getMaintainers($raw = false) + { + if (!isset($this->_packageInfo['lead'])) { + return false; + } + if ($raw) { + $ret = array('lead' => $this->_packageInfo['lead']); + (isset($this->_packageInfo['developer'])) ? + $ret['developer'] = $this->_packageInfo['developer'] :null; + (isset($this->_packageInfo['contributor'])) ? + $ret['contributor'] = $this->_packageInfo['contributor'] :null; + (isset($this->_packageInfo['helper'])) ? + $ret['helper'] = $this->_packageInfo['helper'] :null; + return $ret; + } else { + $ret = array(); + $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] : + array($this->_packageInfo['lead']); + foreach ($leads as $lead) { + $s = $lead; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'lead'; + $ret[] = $s; + } + if (isset($this->_packageInfo['developer'])) { + $leads = isset($this->_packageInfo['developer'][0]) ? + $this->_packageInfo['developer'] : + array($this->_packageInfo['developer']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'developer'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['contributor'])) { + $leads = isset($this->_packageInfo['contributor'][0]) ? + $this->_packageInfo['contributor'] : + array($this->_packageInfo['contributor']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'contributor'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['helper'])) { + $leads = isset($this->_packageInfo['helper'][0]) ? + $this->_packageInfo['helper'] : + array($this->_packageInfo['helper']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'helper'; + $ret[] = $s; + } + } + return $ret; + } + return false; + } + + function getLeads() + { + if (isset($this->_packageInfo['lead'])) { + return $this->_packageInfo['lead']; + } + return false; + } + + function getDevelopers() + { + if (isset($this->_packageInfo['developer'])) { + return $this->_packageInfo['developer']; + } + return false; + } + + function getContributors() + { + if (isset($this->_packageInfo['contributor'])) { + return $this->_packageInfo['contributor']; + } + return false; + } + + function getHelpers() + { + if (isset($this->_packageInfo['helper'])) { + return $this->_packageInfo['helper']; + } + return false; + } + + function setDate($date) + { + if (!isset($this->_packageInfo['date'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), array(), 'date'); + } + $this->_packageInfo['date'] = $date; + $this->_isValid = 0; + } + + function setTime($time) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['time'])) { + // ensure that the time tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), $time, 'time'); + } + $this->_packageInfo['time'] = $time; + } + + function getDate() + { + if (isset($this->_packageInfo['date'])) { + return $this->_packageInfo['date']; + } + return false; + } + + function getTime() + { + if (isset($this->_packageInfo['time'])) { + return $this->_packageInfo['time']; + } + return false; + } + + /** + * @param package|api version category to return + */ + function getVersion($key = 'release') + { + if (isset($this->_packageInfo['version'][$key])) { + return $this->_packageInfo['version'][$key]; + } + return false; + } + + function getStability() + { + if (isset($this->_packageInfo['stability'])) { + return $this->_packageInfo['stability']; + } + return false; + } + + function getState($key = 'release') + { + if (isset($this->_packageInfo['stability'][$key])) { + return $this->_packageInfo['stability'][$key]; + } + return false; + } + + function getLicense($raw = false) + { + if (isset($this->_packageInfo['license'])) { + if ($raw) { + return $this->_packageInfo['license']; + } + if (is_array($this->_packageInfo['license'])) { + return $this->_packageInfo['license']['_content']; + } else { + return $this->_packageInfo['license']; + } + } + return false; + } + + function getLicenseLocation() + { + if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) { + return false; + } + return $this->_packageInfo['license']['attribs']; + } + + function getNotes() + { + if (isset($this->_packageInfo['notes'])) { + return $this->_packageInfo['notes']; + } + return false; + } + + /** + * Return the <usesrole> tag contents, if any + * @return array|false + */ + function getUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + return $this->_packageInfo['usesrole']; + } + return false; + } + + /** + * Return the <usestask> tag contents, if any + * @return array|false + */ + function getUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + return $this->_packageInfo['usestask']; + } + return false; + } + + /** + * This should only be used to retrieve filenames and install attributes + */ + function getFilelist($preserve = false) + { + if (isset($this->_packageInfo['filelist']) && !$preserve) { + return $this->_packageInfo['filelist']; + } + $this->flattenFilelist(); + if ($contents = $this->getContents()) { + $ret = array(); + if (!isset($contents['dir'])) { + return false; + } + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + foreach ($contents['dir']['file'] as $file) { + $name = $file['attribs']['name']; + if (!$preserve) { + $file = $file['attribs']; + } + $ret[$name] = $file; + } + if (!$preserve) { + $this->_packageInfo['filelist'] = $ret; + } + return $ret; + } + return false; + } + + /** + * Return configure options array, if any + * + * @return array|false + */ + function getConfigureOptions() + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $releases = $this->getReleases(); + if (isset($releases[0])) { + $releases = $releases[0]; + } + + if (isset($releases['configureoption'])) { + if (!isset($releases['configureoption'][0])) { + $releases['configureoption'] = array($releases['configureoption']); + } + + for ($i = 0; $i < count($releases['configureoption']); $i++) { + $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs']; + } + + return $releases['configureoption']; + } + + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + /** + * Retrieve a list of files that should be installed on this computer + * @return array + */ + function getInstallationFilelist($forfilecheck = false) + { + $contents = $this->getFilelist(true); + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $base = $contents['dir']['attribs']['baseinstalldir']; + } + if (isset($this->_packageInfo['bundle'])) { + return PEAR::raiseError( + 'Exception: bundles should be handled in download code only'); + } + $release = $this->getReleases(); + if ($release) { + if (!isset($release[0])) { + if (!isset($release['installconditions']) && !isset($release['filelist'])) { + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + $release = array($release); + } + $depchecker = &$this->getPEARDependency2($this->_config, array(), + array('channel' => $this->getChannel(), 'package' => $this->getPackage()), + PEAR_VALIDATE_INSTALLING); + foreach ($release as $instance) { + if (isset($instance['installconditions'])) { + $installconditions = $instance['installconditions']; + if (is_array($installconditions)) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($installconditions as $type => $conditions) { + if (!isset($conditions[0])) { + $conditions = array($conditions); + } + foreach ($conditions as $condition) { + $ret = $depchecker->{"validate{$type}Dependency"}($condition); + if (PEAR::isError($ret)) { + PEAR::popErrorHandling(); + continue 3; // skip this release + } + } + } + PEAR::popErrorHandling(); + } + } + // this is the release to use + if (isset($instance['filelist'])) { + // ignore files + if (isset($instance['filelist']['ignore'])) { + $ignore = isset($instance['filelist']['ignore'][0]) ? + $instance['filelist']['ignore'] : + array($instance['filelist']['ignore']); + foreach ($ignore as $ig) { + unset ($contents[$ig['attribs']['name']]); + } + } + // install files as this name + if (isset($instance['filelist']['install'])) { + $installas = isset($instance['filelist']['install'][0]) ? + $instance['filelist']['install'] : + array($instance['filelist']['install']); + foreach ($installas as $as) { + $contents[$as['attribs']['name']]['attribs']['install-as'] = + $as['attribs']['as']; + } + } + } + if ($forfilecheck) { + foreach ($contents as $file => $attrs) { + $contents[$file] = $attrs['attribs']; + } + } + return $contents; + } + } else { // simple release - no installconditions or install-as + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + // no releases matched + return PEAR::raiseError('No releases in package.xml matched the existing operating ' . + 'system, extensions installed, or architecture, cannot install'); + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function getInstalledLocation($file) + { + if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) { + return $this->_packageInfo['filelist'][$file]['installed_as']; + } + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Retrieve the contents tag + */ + function getContents() + { + if (isset($this->_packageInfo['contents'])) { + return $this->_packageInfo['contents']; + } + return false; + } + + /** + * @param string full path to file + * @param string attribute name + * @param string attribute value + * @param int risky but fast - use this to choose a file based on its position in the list + * of files. Index is zero-based like PHP arrays. + * @return bool success of operation + */ + function setFileAttribute($filename, $attr, $value, $index = false) + { + $this->_isValid = 0; + if (in_array($attr, array('role', 'name', 'baseinstalldir'))) { + $this->_filesValid = false; + } + if ($index !== false && + isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) { + $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value; + return true; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value; + } + return true; + } + } + } + return false; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + /** + * Determines whether this package claims it is compatible with the version of + * the package that has a recommended version dependency + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package + * @return boolean + */ + function isCompatible($pf) + { + if (!isset($this->_packageInfo['compatible'])) { + return false; + } + if (!isset($this->_packageInfo['channel'])) { + return false; + } + $me = $pf->getVersion(); + $compatible = $this->_packageInfo['compatible']; + if (!isset($compatible[0])) { + $compatible = array($compatible); + } + $found = false; + foreach ($compatible as $info) { + if (strtolower($info['name']) == strtolower($pf->getPackage())) { + if (strtolower($info['channel']) == strtolower($pf->getChannel())) { + $found = true; + break; + } + } + } + if (!$found) { + return false; + } + if (isset($info['exclude'])) { + if (!isset($info['exclude'][0])) { + $info['exclude'] = array($info['exclude']); + } + foreach ($info['exclude'] as $exclude) { + if (version_compare($me, $exclude, '==')) { + return false; + } + } + } + if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) { + return true; + } + return false; + } + + /** + * @return array|false + */ + function getCompatible() + { + if (isset($this->_packageInfo['compatible'])) { + return $this->_packageInfo['compatible']; + } + return false; + } + + function getDependencies() + { + if (isset($this->_packageInfo['dependencies'])) { + return $this->_packageInfo['dependencies']; + } + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + /** + * Determines whether the passed in package is a subpackage of this package. + * + * No version checking is done, only name verification. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + */ + function isSubpackage($p) + { + $sub = array(); + if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) { + $sub = $this->_packageInfo['dependencies']['required']['subpackage']; + if (!isset($sub[0])) { + $sub = array($sub); + } + } + if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) { + $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage']; + if (!isset($sub1[0])) { + $sub1 = array($sub1); + } + $sub = array_merge($sub, $sub1); + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $group = $this->_packageInfo['dependencies']['group']; + if (!isset($group[0])) { + $group = array($group); + } + foreach ($group as $deps) { + if (isset($deps['subpackage'])) { + $sub2 = $deps['subpackage']; + if (!isset($sub2[0])) { + $sub2 = array($sub2); + } + $sub = array_merge($sub, $sub2); + } + } + } + foreach ($sub as $dep) { + if (strtolower($dep['name']) == strtolower($p->getPackage())) { + if (isset($dep['channel'])) { + if (strtolower($dep['channel']) == strtolower($p->getChannel())) { + return true; + } + } else { + if ($dep['uri'] == $p->getURI()) { + return true; + } + } + } + } + return false; + } + + function dependsOn($package, $channel) + { + if (!($deps = $this->getDependencies())) { + return false; + } + foreach (array('package', 'subpackage') as $type) { + foreach (array('required', 'optional') as $needed) { + if (isset($deps[$needed][$type])) { + if (!isset($deps[$needed][$type][0])) { + $deps[$needed][$type] = array($deps[$needed][$type]); + } + foreach ($deps[$needed][$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $dep['group'] = array($deps['group']); + } + foreach ($deps['group'] as $group) { + if (isset($group[$type])) { + if (!is_array($group[$type])) { + $group[$type] = array($group[$type]); + } + foreach ($group[$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + } + } + return false; + } + + /** + * Get the contents of a dependency group + * @param string + * @return array|false + */ + function getDependencyGroup($name) + { + $name = strtolower($name); + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + foreach ($groups as $group) { + if (strtolower($group['attribs']['name']) == $name) { + return $group; + } + } + return false; + } + + /** + * Retrieve a partial package.xml 1.0 representation of dependencies + * + * a very limited representation of dependencies is returned by this method. + * The <exclude> tag for excluding certain versions of a dependency is + * completely ignored. In addition, dependency groups are ignored, with the + * assumption that all dependencies in dependency groups are also listed in + * the optional group that work with all dependency groups + * @param boolean return package.xml 2.0 <dependencies> tag + * @return array|false + */ + function getDeps($raw = false, $nopearinstaller = false) + { + if (isset($this->_packageInfo['dependencies'])) { + if ($raw) { + return $this->_packageInfo['dependencies']; + } + $ret = array(); + $map = array( + 'php' => 'php', + 'package' => 'pkg', + 'subpackage' => 'pkg', + 'extension' => 'ext', + 'os' => 'os', + 'pearinstaller' => 'pkg', + ); + foreach (array('required', 'optional') as $type) { + $optional = ($type == 'optional') ? 'yes' : 'no'; + if (!isset($this->_packageInfo['dependencies'][$type]) + || empty($this->_packageInfo['dependencies'][$type])) { + continue; + } + foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) { + if ($dtype == 'pearinstaller' && $nopearinstaller) { + continue; + } + if (!isset($deps[0])) { + $deps = array($deps); + } + foreach ($deps as $dep) { + if (!isset($map[$dtype])) { + // no support for arch type + continue; + } + if ($dtype == 'pearinstaller') { + $dep['name'] = 'PEAR'; + $dep['channel'] = 'pear.php.net'; + } + $s = array('type' => $map[$dtype]); + if (isset($dep['channel'])) { + $s['channel'] = $dep['channel']; + } + if (isset($dep['uri'])) { + $s['uri'] = $dep['uri']; + } + if (isset($dep['name'])) { + $s['name'] = $dep['name']; + } + if (isset($dep['conflicts'])) { + $s['rel'] = 'not'; + } else { + if (!isset($dep['min']) && + !isset($dep['max'])) { + $s['rel'] = 'has'; + $s['optional'] = $optional; + } elseif (isset($dep['min']) && + isset($dep['max'])) { + $s['rel'] = 'ge'; + $s1 = $s; + $s1['rel'] = 'le'; + $s['version'] = $dep['min']; + $s1['version'] = $dep['max']; + if (isset($dep['channel'])) { + $s1['channel'] = $dep['channel']; + } + if ($dtype != 'php') { + $s['name'] = $dep['name']; + $s1['name'] = $dep['name']; + } + $s['optional'] = $optional; + $s1['optional'] = $optional; + $ret[] = $s1; + } elseif (isset($dep['min'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['min']) { + $s['rel'] = 'gt'; + } else { + $s['rel'] = 'ge'; + } + $s['version'] = $dep['min']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } elseif (isset($dep['max'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['max']) { + $s['rel'] = 'lt'; + } else { + $s['rel'] = 'le'; + } + $s['version'] = $dep['max']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } + } + $ret[] = $s; + } + } + } + if (count($ret)) { + return $ret; + } + } + return false; + } + + /** + * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false + */ + function getPackageType() + { + if (isset($this->_packageInfo['phprelease'])) { + return 'php'; + } + if (isset($this->_packageInfo['extsrcrelease'])) { + return 'extsrc'; + } + if (isset($this->_packageInfo['extbinrelease'])) { + return 'extbin'; + } + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return 'zendextsrc'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return 'zendextbin'; + } + if (isset($this->_packageInfo['bundle'])) { + return 'bundle'; + } + return false; + } + + /** + * @return array|false + */ + function getReleases() + { + $type = $this->getPackageType(); + if ($type != 'bundle') { + $type .= 'release'; + } + if ($this->getPackageType() && isset($this->_packageInfo[$type])) { + return $this->_packageInfo[$type]; + } + return false; + } + + /** + * @return array + */ + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function hasDeps() + { + return isset($this->_packageInfo['dependencies']); + } + + function getPackagexmlVersion() + { + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return '2.1'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return '2.1'; + } + return '2.0'; + } + + /** + * @return array|false + */ + function getSourcePackage() + { + if (isset($this->_packageInfo['extbinrelease']) || + isset($this->_packageInfo['zendextbinrelease'])) { + return array('channel' => $this->_packageInfo['srcchannel'], + 'package' => $this->_packageInfo['srcpackage']); + } + return false; + } + + function getBundledPackages() + { + if (isset($this->_packageInfo['bundle'])) { + return $this->_packageInfo['contents']['bundledpackage']; + } + return false; + } + + function getLastModified() + { + if (isset($this->_packageInfo['_lastmodified'])) { + return $this->_packageInfo['_lastmodified']; + } + return false; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + function &getRW() + { + if (!class_exists('PEAR_PackageFile_v2_rw')) { + require_once 'PEAR/PackageFile/v2/rw.php'; + } + $a = new PEAR_PackageFile_v2_rw; + foreach (get_object_vars($this) as $name => $unused) { + if (!isset($this->$name)) { + continue; + } + if ($name == '_config' || $name == '_logger'|| $name == '_registry' || + $name == '_stack') { + $a->$name = &$this->$name; + } else { + $a->$name = $this->$name; + } + } + return $a; + } + + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v2')) { + require_once 'PEAR/PackageFile/Generator/v2.php'; + } + $a = &new PEAR_PackageFile_Generator_v2($this); + return $a; + } + + function analyzeSourceCode($file, $string = false) + { + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + return $this->_v2Validator->analyzeSourceCode($file, $string); + } + + function validate($state = PEAR_VALIDATE_NORMAL) + { + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + if (isset($this->_packageInfo['xsdversion'])) { + unset($this->_packageInfo['xsdversion']); + } + return $this->_v2Validator->validate($this, $state); + } + + function getTasksNs() + { + if (!isset($this->_tasksNs)) { + if (isset($this->_packageInfo['attribs'])) { + foreach ($this->_packageInfo['attribs'] as $name => $value) { + if ($value == 'http://pear.php.net/dtd/tasks-1.0') { + $this->_tasksNs = str_replace('xmlns:', '', $name); + break; + } + } + } + } + return $this->_tasksNs; + } + + /** + * Determine whether a task name is a valid task. Custom tasks may be defined + * using subdirectories by putting a "-" in the name, as in <tasks:mycustom-task> + * + * Note that this method will auto-load the task class file and test for the existence + * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class + * PEAR_Task_mycustom_task + * @param string + * @return boolean + */ + function getTask($task) + { + $this->getTasksNs(); + // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace + $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task); + $taskfile = str_replace(' ', '/', ucwords($task)); + $task = str_replace(array(' ', '/'), '_', ucwords($task)); + if (class_exists("PEAR_Task_$task")) { + return "PEAR_Task_$task"; + } + $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true); + if ($fp) { + fclose($fp); + require_once "PEAR/Task/$taskfile.php"; + return "PEAR_Task_$task"; + } + return false; + } + + /** + * Key-friendly array_splice + * @param tagname to splice a value in before + * @param mixed the value to splice in + * @param string the new tag name + */ + function _ksplice($array, $key, $value, $newkey) + { + $offset = array_search($key, array_keys($array)); + $after = array_slice($array, $offset); + $before = array_slice($array, 0, $offset); + $before[$newkey] = $value; + return array_merge($before, $after); + } + + /** + * @param array a list of possible keys, in the order they may occur + * @param mixed contents of the new package.xml tag + * @param string tag name + * @access private + */ + function _insertBefore($array, $keys, $contents, $newkey) + { + foreach ($keys as $key) { + if (isset($array[$key])) { + return $array = $this->_ksplice($array, $key, $contents, $newkey); + } + } + $array[$newkey] = $contents; + return $array; + } + + /** + * @param subsection of {@link $_packageInfo} + * @param array|string tag contents + * @param array format: + * <pre> + * array( + * tagname => array(list of tag names that follow this one), + * childtagname => array(list of child tag names that follow this one), + * ) + * </pre> + * + * This allows construction of nested tags + * @access private + */ + function _mergeTag($manip, $contents, $order) + { + if (count($order)) { + foreach ($order as $tag => $curorder) { + if (!isset($manip[$tag])) { + // ensure that the tag is set up + $manip = $this->_insertBefore($manip, $curorder, array(), $tag); + } + if (count($order) > 1) { + $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1)); + return $manip; + } + } + } else { + return $manip; + } + if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) { + $manip[$tag][] = $contents; + } else { + if (!count($manip[$tag])) { + $manip[$tag] = $contents; + } else { + $manip[$tag] = array($manip[$tag]); + $manip[$tag][] = $contents; + } + } + return $manip; + } +} +?> diff --git a/includes/pear/PEAR/PackageFile/v2/Validator.php b/includes/pear/PEAR/PackageFile/v2/Validator.php new file mode 100644 index 0000000..7ce2fb3 --- /dev/null +++ b/includes/pear/PEAR/PackageFile/v2/Validator.php @@ -0,0 +1,2154 @@ +<?php +/** + * PEAR_PackageFile_v2, package.xml version 2.0, read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its + * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + * @access private + */ +class PEAR_PackageFile_v2_Validator +{ + /** + * @var array + */ + var $_packageInfo; + /** + * @var PEAR_PackageFile_v2 + */ + var $_pf; + /** + * @var PEAR_ErrorStack + */ + var $_stack; + /** + * @var int + */ + var $_isValid = 0; + /** + * @var int + */ + var $_filesValid = 0; + /** + * @var int + */ + var $_curState = 0; + /** + * @param PEAR_PackageFile_v2 + * @param int + */ + function validate(&$pf, $state = PEAR_VALIDATE_NORMAL) + { + $this->_pf = &$pf; + $this->_curState = $state; + $this->_packageInfo = $this->_pf->getArray(); + $this->_isValid = $this->_pf->_isValid; + $this->_filesValid = $this->_pf->_filesValid; + $this->_stack = &$pf->_stack; + $this->_stack->getErrors(true); + if (($this->_isValid & $state) == $state) { + return true; + } + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_packageInfo['attribs']['version']) || + ($this->_packageInfo['attribs']['version'] != '2.0' && + $this->_packageInfo['attribs']['version'] != '2.1') + ) { + $this->_noPackageVersion(); + } + $structure = + array( + 'name', + 'channel|uri', + '*extends', // can't be multiple, but this works fine + 'summary', + 'description', + '+lead', // these all need content checks + '*developer', + '*contributor', + '*helper', + 'date', + '*time', + 'version', + 'stability', + 'license->?uri->?filesource', + 'notes', + 'contents', //special validation needed + '*compatible', + 'dependencies', //special validation needed + '*usesrole', + '*usestask', // reserve these for 1.4.0a1 to implement + // this will allow a package.xml to gracefully say it + // needs a certain package installed in order to implement a role or task + '*providesextension', + '*srcpackage|*srcuri', + '+phprelease|+extsrcrelease|+extbinrelease|' . + '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed + '*changelog', + ); + $test = $this->_packageInfo; + if (isset($test['dependencies']) && + isset($test['dependencies']['required']) && + isset($test['dependencies']['required']['pearinstaller']) && + isset($test['dependencies']['required']['pearinstaller']['min']) && + version_compare('1.9.4', + $test['dependencies']['required']['pearinstaller']['min'], '<') + ) { + $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']); + return false; + } + // ignore post-installation array fields + if (array_key_exists('filelist', $test)) { + unset($test['filelist']); + } + if (array_key_exists('_lastmodified', $test)) { + unset($test['_lastmodified']); + } + if (array_key_exists('#binarypackage', $test)) { + unset($test['#binarypackage']); + } + if (array_key_exists('old', $test)) { + unset($test['old']); + } + if (array_key_exists('_lastversion', $test)) { + unset($test['_lastversion']); + } + if (!$this->_stupidSchemaValidate($structure, $test, '<package>')) { + return false; + } + if (empty($this->_packageInfo['name'])) { + $this->_tagCannotBeEmpty('name'); + } + $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel'; + if (empty($this->_packageInfo[$test])) { + $this->_tagCannotBeEmpty($test); + } + if (is_array($this->_packageInfo['license']) && + (!isset($this->_packageInfo['license']['_content']) || + empty($this->_packageInfo['license']['_content']))) { + $this->_tagCannotBeEmpty('license'); + } elseif (empty($this->_packageInfo['license'])) { + $this->_tagCannotBeEmpty('license'); + } + if (empty($this->_packageInfo['summary'])) { + $this->_tagCannotBeEmpty('summary'); + } + if (empty($this->_packageInfo['description'])) { + $this->_tagCannotBeEmpty('description'); + } + if (empty($this->_packageInfo['date'])) { + $this->_tagCannotBeEmpty('date'); + } + if (empty($this->_packageInfo['notes'])) { + $this->_tagCannotBeEmpty('notes'); + } + if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) { + $this->_tagCannotBeEmpty('time'); + } + if (isset($this->_packageInfo['dependencies'])) { + $this->_validateDependencies(); + } + if (isset($this->_packageInfo['compatible'])) { + $this->_validateCompatible(); + } + if (!isset($this->_packageInfo['bundle'])) { + if (empty($this->_packageInfo['contents'])) { + $this->_tagCannotBeEmpty('contents'); + } + if (!isset($this->_packageInfo['contents']['dir'])) { + $this->_filelistMustContainDir('contents'); + return false; + } + if (isset($this->_packageInfo['contents']['file'])) { + $this->_filelistCannotContainFile('contents'); + return false; + } + } + $this->_validateMaintainers(); + $this->_validateStabilityVersion(); + $fail = false; + if (array_key_exists('usesrole', $this->_packageInfo)) { + $roles = $this->_packageInfo['usesrole']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['role'])) { + $this->_usesroletaskMustHaveRoleTask('usesrole', 'role'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['role'], 'usesrole'); + $fail = true; + } + } + } + } + if (array_key_exists('usestask', $this->_packageInfo)) { + $roles = $this->_packageInfo['usestask']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['task'])) { + $this->_usesroletaskMustHaveRoleTask('usestask', 'task'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['task'], 'usestask'); + $fail = true; + } + } + } + } + + if ($fail) { + return false; + } + + $list = $this->_packageInfo['contents']; + if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) { + $this->_multipleToplevelDirNotAllowed(); + return $this->_isValid = 0; + } + + $this->_validateFilelist(); + $this->_validateRelease(); + if (!$this->_stack->hasErrors()) { + $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true); + if (PEAR::isError($chan)) { + $this->_unknownChannel($this->_pf->getChannel()); + } else { + $valpack = $chan->getValidationPackage(); + // for channel validator packages, always use the default PEAR validator. + // otherwise, they can't be installed or packaged + $validator = $chan->getValidationObject($this->_pf->getPackage()); + if (!$validator) { + $this->_stack->push(__FUNCTION__, 'error', + array('channel' => $chan->getName(), + 'package' => $this->_pf->getPackage(), + 'name' => $valpack['_content'], + 'version' => $valpack['attribs']['version']), + 'package "%channel%/%package%" cannot be properly validated without ' . + 'validation package "%channel%/%name%-%version%"'); + return $this->_isValid = 0; + } + $validator->setPackageFile($this->_pf); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_stack->push(__FUNCTION__, 'error', $error, + 'Channel validator error: field "%field%" - %reason%'); + } + foreach ($failures['warnings'] as $warning) { + $this->_stack->push(__FUNCTION__, 'warning', $warning, + 'Channel validator warning: field "%field%" - %reason%'); + } + } + } + + $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error'); + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) { + if ($this->_pf->getPackageType() == 'bundle') { + if ($this->_analyzeBundledPackages()) { + $this->_filesValid = $this->_pf->_filesValid = true; + } else { + $this->_pf->_isValid = $this->_isValid = 0; + } + } else { + if (!$this->_analyzePhpFiles()) { + $this->_pf->_isValid = $this->_isValid = 0; + } else { + $this->_filesValid = $this->_pf->_filesValid = true; + } + } + } + + if ($this->_isValid) { + return $this->_pf->_isValid = $this->_isValid = $state; + } + + return $this->_pf->_isValid = $this->_isValid = 0; + } + + function _stupidSchemaValidate($structure, $xml, $root) + { + if (!is_array($xml)) { + $xml = array(); + } + $keys = array_keys($xml); + reset($keys); + $key = current($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $ret = true; + $mismatch = false; + foreach ($structure as $struc) { + if ($key) { + $tag = $xml[$key]; + } + $test = $this->_processStructure($struc); + if (isset($test['choices'])) { + $loose = true; + foreach ($test['choices'] as $choice) { + if ($key == $choice['tag']) { + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $mismatch = false; + if ($key && $key != $choice['tag'] && isset($choice['multiple'])) { + $unfoundtags[] = $choice['tag']; + $optionaltags[] = $choice['tag']; + if ($key) { + $mismatch = true; + } + } + $ret &= $this->_processAttribs($choice, $tag, $root); + continue 2; + } else { + $unfoundtags[] = $choice['tag']; + $mismatch = true; + } + if (!isset($choice['multiple']) || $choice['multiple'] != '*') { + $loose = false; + } else { + $optionaltags[] = $choice['tag']; + } + } + if (!$loose) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + } else { + if ($key != $test['tag']) { + if (isset($test['multiple']) && $test['multiple'] != '*') { + $unfoundtags[] = $test['tag']; + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } else { + if ($key) { + $mismatch = true; + } + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + } + if (!isset($test['multiple'])) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + continue; + } else { + $unfoundtags = $optionaltags = array(); + $mismatch = false; + } + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + if ($key && $key != $test['tag'] && isset($test['multiple'])) { + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + $mismatch = true; + } + $ret &= $this->_processAttribs($test, $tag, $root); + continue; + } + } + if (!$mismatch && count($optionaltags)) { + // don't error out on any optional tags + $unfoundtags = array_diff($unfoundtags, $optionaltags); + } + if (count($unfoundtags)) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + } elseif ($key) { + // unknown tags + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + while ($key = next($keys)) { + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + } + } + return $ret; + } + + function _processAttribs($choice, $tag, $context) + { + if (isset($choice['attribs'])) { + if (!is_array($tag)) { + $tag = array($tag); + } + $tags = $tag; + if (!isset($tags[0])) { + $tags = array($tags); + } + $ret = true; + foreach ($tags as $i => $tag) { + if (!is_array($tag) || !isset($tag['attribs'])) { + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + $ret &= $this->_tagHasNoAttribs($choice['tag'], + $context); + continue 2; + } + } + } + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + if (!isset($tag['attribs'][$attrib])) { + $ret &= $this->_tagMissingAttribute($choice['tag'], + $attrib, $context); + } + } + } + } + return $ret; + } + return true; + } + + function _processStructure($key) + { + $ret = array(); + if (count($pieces = explode('|', $key)) > 1) { + $ret['choices'] = array(); + foreach ($pieces as $piece) { + $ret['choices'][] = $this->_processStructure($piece); + } + return $ret; + } + $multi = $key{0}; + if ($multi == '+' || $multi == '*') { + $ret['multiple'] = $key{0}; + $key = substr($key, 1); + } + if (count($attrs = explode('->', $key)) > 1) { + $ret['tag'] = array_shift($attrs); + $ret['attribs'] = $attrs; + } else { + $ret['tag'] = $key; + } + return $ret; + } + + function _validateStabilityVersion() + { + $structure = array('release', 'api'); + $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>'); + $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>'); + if ($a) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['release'])) { + $this->_invalidVersion('release', $this->_packageInfo['version']['release']); + } + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['api'])) { + $this->_invalidVersion('api', $this->_packageInfo['version']['api']); + } + if (!in_array($this->_packageInfo['stability']['release'], + array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('release', $this->_packageInfo['stability']['release']); + } + if (!in_array($this->_packageInfo['stability']['api'], + array('devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('api', $this->_packageInfo['stability']['api']); + } + } + } + + function _validateMaintainers() + { + $structure = + array( + 'name', + 'user', + 'email', + 'active', + ); + foreach (array('lead', 'developer', 'contributor', 'helper') as $type) { + if (!isset($this->_packageInfo[$type])) { + continue; + } + if (isset($this->_packageInfo[$type][0])) { + foreach ($this->_packageInfo[$type] as $lead) { + $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>'); + } + } else { + $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type], + '<' . $type . '>'); + } + } + } + + function _validatePhpDep($dep, $installcondition = false) + { + $structure = array( + 'min', + '*max', + '*exclude', + ); + $type = $installcondition ? '<installcondition><php>' : '<dependencies><required><php>'; + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['min'])) { + $this->_invalidVersion($type . '<min>', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['max'])) { + $this->_invalidVersion($type . '<max>', $dep['max']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match( + '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $exclude)) { + $this->_invalidVersion($type . '<exclude>', $exclude); + } + } + } + } + + function _validatePearinstallerDep($dep) + { + $structure = array( + 'min', + '*max', + '*recommended', + '*exclude', + ); + $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>'); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('<dependencies><required><pearinstaller><min>', + $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('<dependencies><required><pearinstaller><max>', + $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('<dependencies><required><pearinstaller><recommended>', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('<dependencies><required><pearinstaller><exclude>', + $exclude); + } + } + } + } + + function _validatePackageDep($dep, $group, $type = '<package>') + { + if (isset($dep['uri'])) { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'uri', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'uri', + '*providesextension', + ); + } + } else { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*exclude', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*recommended', + '*exclude', + '*nodefault', + '*providesextension', + ); + } + } + if (isset($dep['name'])) { + $type .= '<name>' . $dep['name'] . '</name>'; + } + $this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type); + if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) || + isset($dep['recommended']) || isset($dep['exclude']))) { + $this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type); + } + if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') { + $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type); + } + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>', + $exclude); + } + } + } + } + + function _validateSubpackageDep($dep, $group) + { + $this->_validatePackageDep($dep, $group, '<subpackage>'); + if (isset($dep['providesextension'])) { + $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : ''); + } + if (isset($dep['conflicts'])) { + $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : ''); + } + } + + function _validateExtensionDep($dep, $group = false, $installcondition = false) + { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + '*min', + '*max', + '*exclude', + 'conflicts', + ); + } else { + $structure = array( + 'name', + '*min', + '*max', + '*recommended', + '*exclude', + ); + } + if ($installcondition) { + $type = '<installcondition><extension>'; + } else { + $type = '<dependencies>' . $group . '<extension>'; + } + if (isset($dep['name'])) { + $type .= '<name>' . $dep['name'] . '</name>'; + } + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion(substr($type, 1) . '<min', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion(substr($type, 1) . '<recommended', $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude); + } + } + } + } + + function _validateOsDep($dep, $installcondition = false) + { + $structure = array( + 'name', + '*conflicts', + ); + $type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>'; + if ($this->_stupidSchemaValidate($structure, $dep, $type)) { + if ($dep['name'] == '*') { + if (array_key_exists('conflicts', $dep)) { + $this->_cannotConflictWithAllOs($type); + } + } + } + } + + function _validateArchDep($dep, $installcondition = false) + { + $structure = array( + 'pattern', + '*conflicts', + ); + $type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>'; + $this->_stupidSchemaValidate($structure, $dep, $type); + } + + function _validateInstallConditions($cond, $release) + { + $structure = array( + '*php', + '*extension', + '*os', + '*arch', + ); + if (!$this->_stupidSchemaValidate($structure, + $cond, $release)) { + return false; + } + foreach (array('php', 'extension', 'os', 'arch') as $type) { + if (isset($cond[$type])) { + $iter = $cond[$type]; + if (!is_array($iter) || !isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type == 'extension') { + $this->{"_validate{$type}Dep"}($package, false, true); + } else { + $this->{"_validate{$type}Dep"}($package, true); + } + } + } + } + } + + function _validateDependencies() + { + $structure = array( + 'required', + '*optional', + '*group->name->hint' + ); + if (!$this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'], '<dependencies>')) { + return false; + } + foreach (array('required', 'optional') as $simpledep) { + if (isset($this->_packageInfo['dependencies'][$simpledep])) { + if ($simpledep == 'optional') { + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + } else { + $structure = array( + 'php', + 'pearinstaller', + '*package', + '*subpackage', + '*extension', + '*os', + '*arch', + ); + } + if ($this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'][$simpledep], + "<dependencies><$simpledep>")) { + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannel($type, + $package['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannel($type, $package['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, "<$simpledep>"); + } + } + } + if ($simpledep == 'optional') { + continue; + } + foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + $this->{"_validate{$type}Dep"}($package); + } + } + } + } + } + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + foreach ($groups as $group) { + if ($this->_stupidSchemaValidate($structure, $group, '<group>')) { + if (!PEAR_Validate::validGroupName($group['attribs']['name'])) { + $this->_invalidDepGroupName($group['attribs']['name']); + } + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($group[$type])) { + $iter = $group[$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannelGroup($type, + $package['name'], + $group['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannelGroup($type, + $package['name'], + $group['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, '<group name="' . + $group['attribs']['name'] . '">'); + } + } + } + } + } + } + } + + function _validateCompatible() + { + $compat = $this->_packageInfo['compatible']; + if (!isset($compat[0])) { + $compat = array($compat); + } + $required = array('name', 'channel', 'min', 'max', '*exclude'); + foreach ($compat as $package) { + $type = '<compatible>'; + if (is_array($package) && array_key_exists('name', $package)) { + $type .= '<name>' . $package['name'] . '</name>'; + } + $this->_stupidSchemaValidate($required, $package, $type); + if (is_array($package) && array_key_exists('min', $package)) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $package['min'])) { + $this->_invalidVersion(substr($type, 1) . '<min', $package['min']); + } + } + if (is_array($package) && array_key_exists('max', $package)) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $package['max'])) { + $this->_invalidVersion(substr($type, 1) . '<max', $package['max']); + } + } + if (is_array($package) && array_key_exists('exclude', $package)) { + if (!is_array($package['exclude'])) { + $package['exclude'] = array($package['exclude']); + } + foreach ($package['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude); + } + } + } + } + } + + function _validateBundle($list) + { + if (!is_array($list) || !isset($list['bundledpackage'])) { + return $this->_NoBundledPackages(); + } + if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) { + return $this->_AtLeast2BundledPackages(); + } + foreach ($list['bundledpackage'] as $package) { + if (!is_string($package)) { + $this->_bundledPackagesMustBeFilename(); + } + } + } + + function _validateFilelist($list = false, $allowignore = false, $dirs = '') + { + $iscontents = false; + if (!$list) { + $iscontents = true; + $list = $this->_packageInfo['contents']; + if (isset($this->_packageInfo['bundle'])) { + return $this->_validateBundle($list); + } + } + if ($allowignore) { + $struc = array( + '*install->name->as', + '*ignore->name' + ); + } else { + $struc = array( + '*dir->name->?baseinstalldir', + '*file->name->role->?baseinstalldir->?md5sum' + ); + if (isset($list['dir']) && isset($list['file'])) { + // stave off validation errors without requiring a set order. + $_old = $list; + if (isset($list['attribs'])) { + $list = array('attribs' => $_old['attribs']); + } + $list['dir'] = $_old['dir']; + $list['file'] = $_old['file']; + } + } + if (!isset($list['attribs']) || !isset($list['attribs']['name'])) { + $unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">'; + $dirname = $iscontents ? '<contents>' : $unknown; + } else { + $dirname = '<dir name="' . $list['attribs']['name'] . '">'; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $list['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidDirName($list['attribs']['name']); + } + } + $res = $this->_stupidSchemaValidate($struc, $list, $dirname); + if ($allowignore && $res) { + $ignored_or_installed = array(); + $this->_pf->getFilelist(); + $fcontents = $this->_pf->getContents(); + $filelist = array(); + if (!isset($fcontents['dir']['file'][0])) { + $fcontents['dir']['file'] = array($fcontents['dir']['file']); + } + foreach ($fcontents['dir']['file'] as $file) { + $filelist[$file['attribs']['name']] = true; + } + if (isset($list['install'])) { + if (!isset($list['install'][0])) { + $list['install'] = array($list['install']); + } + foreach ($list['install'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'install'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_multipleInstallAs($file['attribs']['name']); + } + if (!isset($ignored_or_installed[$file['attribs']['name']])) { + $ignored_or_installed[$file['attribs']['name']] = array(); + } + $ignored_or_installed[$file['attribs']['name']][] = 1; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['as']))) { + // file contains .. parent directory or . cur directory references + $this->_invalidFileInstallAs($file['attribs']['name'], + $file['attribs']['as']); + } + } + } + if (isset($list['ignore'])) { + if (!isset($list['ignore'][0])) { + $list['ignore'] = array($list['ignore']); + } + foreach ($list['ignore'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'ignore'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_ignoreAndInstallAs($file['attribs']['name']); + } + } + } + } + if (!$allowignore && isset($list['file'])) { + if (is_string($list['file'])) { + $this->_oldStyleFileNotAllowed(); + return false; + } + if (!isset($list['file'][0])) { + // single file + $list['file'] = array($list['file']); + } + foreach ($list['file'] as $i => $file) + { + if (isset($file['attribs']) && isset($file['attribs']['name'])) { + if ($file['attribs']['name']{0} == '.' && + $file['attribs']['name']{1} == '/') { + // name is something like "./doc/whatever.txt" + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + } + if (isset($file['attribs']) && isset($file['attribs']['role'])) { + if (!$this->_validateRole($file['attribs']['role'])) { + if (isset($this->_packageInfo['usesrole'])) { + $roles = $this->_packageInfo['usesrole']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['role'] = $file['attribs']['role']) { + $msg = 'This package contains role "%role%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('role' => $role['role'], + 'package' => $role['uri']); + } else { + $params = array('role' => $role['role'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallRole', 'error', $params, $msg); + } + } + } + $this->_invalidFileRole($file['attribs']['name'], + $dirname, $file['attribs']['role']); + } + } + if (!isset($file['attribs'])) { + continue; + } + $save = $file['attribs']; + if ($dirs) { + $save['name'] = $dirs . '/' . $save['name']; + } + unset($file['attribs']); + if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks + foreach ($file as $task => $value) { + if ($tagClass = $this->_pf->getTask($task)) { + if (!is_array($value) || !isset($value[0])) { + $value = array($value); + } + foreach ($value as $v) { + $ret = call_user_func(array($tagClass, 'validateXml'), + $this->_pf, $v, $this->_pf->_config, $save); + if (is_array($ret)) { + $this->_invalidTask($task, $ret, isset($save['name']) ? + $save['name'] : ''); + } + } + } else { + if (isset($this->_packageInfo['usestask'])) { + $roles = $this->_packageInfo['usestask']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['task'] = $task) { + $msg = 'This package contains task "%task%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('task' => $role['task'], + 'package' => $role['uri']); + } else { + $params = array('task' => $role['task'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallTask', 'error', + $params, $msg); + } + } + } + $this->_unknownTask($task, $save['name']); + } + } + } + } + } + if (isset($list['ignore'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('ignore'); + } + } + if (isset($list['install'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('install'); + } + } + if (isset($list['file'])) { + if ($allowignore) { + $this->_fileNotAllowed('file'); + } + } + if (isset($list['dir'])) { + if ($allowignore) { + $this->_fileNotAllowed('dir'); + } else { + if (!isset($list['dir'][0])) { + $list['dir'] = array($list['dir']); + } + foreach ($list['dir'] as $dir) { + if (isset($dir['attribs']) && isset($dir['attribs']['name'])) { + if ($dir['attribs']['name'] == '/' || + !isset($this->_packageInfo['contents']['dir']['dir'])) { + // always use nothing if the filelist has already been flattened + $newdirs = ''; + } elseif ($dirs == '') { + $newdirs = $dir['attribs']['name']; + } else { + $newdirs = $dirs . '/' . $dir['attribs']['name']; + } + } else { + $newdirs = $dirs; + } + $this->_validateFilelist($dir, $allowignore, $newdirs); + } + } + } + } + + function _validateRelease() + { + if (isset($this->_packageInfo['phprelease'])) { + $release = 'phprelease'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['phprelease']; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, '<phprelease>'); + } + } + foreach (array('', 'zend') as $prefix) { + $releasetype = $prefix . 'extsrcrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*configureoption->name->prompt->?default', + '*binarypackage', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + if (isset($rel['binarypackage'])) { + if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) { + $rel['binarypackage'] = array($rel['binarypackage']); + } + foreach ($rel['binarypackage'] as $bin) { + if (!is_string($bin)) { + $this->_binaryPackageMustBePackagename(); + } + } + } + } + } + $releasetype = 'extbinrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['channel']) && + !isset($this->_packageInfo['srcpackage'])) { + $this->_mustSrcPackage($release); + } + if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) { + $this->_mustSrcuri($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + } + } + } + if (isset($this->_packageInfo['bundle'])) { + $release = 'bundle'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['bundle']; + if (!is_array($releases) || !isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, '<bundle>'); + } + } + foreach ($releases as $rel) { + if (is_array($rel) && array_key_exists('installconditions', $rel)) { + $this->_validateInstallConditions($rel['installconditions'], + "<$release><installconditions>"); + } + if (is_array($rel) && array_key_exists('filelist', $rel)) { + if ($rel['filelist']) { + + $this->_validateFilelist($rel['filelist'], true); + } + } + } + } + + /** + * This is here to allow role extension through plugins + * @param string + */ + function _validateRole($role) + { + return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())); + } + + function _pearVersionTooLow($version) + { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $version), + 'This package.xml requires PEAR version %version% to parse properly, we are ' . + 'version 1.9.4'); + } + + function _invalidTagOrder($oktags, $actual, $root) + { + $this->_stack->push(__FUNCTION__, 'error', + array('oktags' => $oktags, 'actual' => $actual, 'root' => $root), + 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"'); + } + + function _ignoreNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside global <contents>, only inside ' . + '<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only'); + } + + function _fileNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside release <filelist>, only inside ' . + '<contents>, use <ignore> and <install> only'); + } + + function _oldStyleFileNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Old-style <file>name</file> is not allowed. Use' . + '<file name="name" role="role"/>'); + } + + function _tagMissingAttribute($tag, $attr, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'attribute' => $attr, 'context' => $context), + 'tag <%tag%> in context "%context%" has no attribute "%attribute%"'); + } + + function _tagHasNoAttribs($tag, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'context' => $context), + 'tag <%tag%> has no attributes in context "%context%"'); + } + + function _invalidInternalStructure() + { + $this->_stack->push(__FUNCTION__, 'exception', array(), + 'internal array was not generated by compatible parser, or extreme parser error, cannot continue'); + } + + function _invalidFileRole($file, $dir, $role) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'dir' => $dir, 'role' => $role, + 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())), + 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%'); + } + + function _invalidFileName($file, $dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file), + 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _invalidFileInstallAs($file, $as) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'as' => $as), + 'File "%file%" <install as="%as%"/> cannot contain "./" or contain ".."'); + } + + function _invalidDirName($dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'dir' => $file), + 'Directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _filelistCannotContainFile($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> can only contain <dir>, contains <file>. Use ' . + '<dir name="/"> as the first dir element'); + } + + function _filelistMustContainDir($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> must contain <dir>. Use <dir name="/"> as the ' . + 'first dir element'); + } + + function _tagCannotBeEmpty($tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '<%tag%> cannot be empty (<%tag%/>)'); + } + + function _UrlOrChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _UrlOrChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _unknownChannel($channel) + { + $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel), + 'Unknown channel "%channel%"'); + } + + function _noPackageVersion() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'package.xml <package> tag has no version attribute, or version is not 2.0'); + } + + function _NoBundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'No <bundledpackage> tag was found in <contents>, required for bundle packages'); + } + + function _AtLeast2BundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'At least 2 packages must be bundled in a bundle package'); + } + + function _ChannelOrUri($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Bundled package "%name%" can have either a uri or a channel, not both'); + } + + function _noChildTag($child, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag), + 'Tag <%tag%> is missing child tag <%child%>'); + } + + function _invalidVersion($type, $value) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value), + 'Version type <%type%> is not a valid version (%value%)'); + } + + function _invalidState($type, $value) + { + $states = array('stable', 'beta', 'alpha', 'devel'); + if ($type != 'api') { + $states[] = 'snapshot'; + } + if (strtolower($value) == 'rc') { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $this->_packageInfo['version']['release']), + 'RC is not a state, it is a version postfix, try %version%RC1, stability beta'); + } + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value, + 'types' => $states), + 'Stability type <%type%> is not a valid stability (%value%), must be one of ' . + '%types%'); + } + + function _invalidTask($task, $ret, $file) + { + switch ($ret[0]) { + case PEAR_TASK_ERROR_MISSING_ATTRIB : + $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%'; + break; + case PEAR_TASK_ERROR_NOATTRIBS : + $info = array('task' => $task, 'file' => $file); + $msg = 'task <%task%> has no attributes in file %file%'; + break; + case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE : + $info = array('attrib' => $ret[1], 'values' => $ret[3], + 'was' => $ret[2], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '. + 'in file %file%, expecting one of "%values%"'; + break; + case PEAR_TASK_ERROR_INVALID : + $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> in file %file% is invalid because of "%reason%"'; + break; + } + $this->_stack->push(__FUNCTION__, 'error', $info, $msg); + } + + function _unknownTask($task, $file) + { + $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file), + 'Unknown task "%task%" passed in file <file name="%file%">'); + } + + function _subpackageCannotProvideExtension($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use <providesextension>, ' . + 'only package dependencies can use this tag'); + } + + function _subpackagesCannotConflict($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use <conflicts/>, ' . + 'only package dependencies can use this tag'); + } + + function _cannotProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot use <providesextension>, only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension'); + } + + function _mustProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages must use <providesextension> to indicate which PHP extension is provided'); + } + + function _cannotHaveSrcpackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot specify a source code package, only extension binaries may use the <srcpackage> tag'); + } + + function _mustSrcPackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>'); + } + + function _mustSrcuri($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>'); + } + + function _uriDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: dependencies with a <uri> tag cannot have any versioning information'); + } + + function _conflictingDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: conflicting dependencies cannot have versioning info, use <exclude> to ' . + 'exclude specific versions of a dependency'); + } + + function _DepchannelCannotBeUri($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' . + 'dependencies only'); + } + + function _bundledPackagesMustBeFilename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + '<bundledpackage> tags must contain only the filename of a package release ' . + 'in the bundle'); + } + + function _binaryPackageMustBePackagename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + '<binarypackage> tags must contain the name of a package that is ' . + 'a compiled version of this extsrc/zendextsrc package'); + } + + function _fileNotFound($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'File "%file%" in package.xml does not exist'); + } + + function _notInContents($file, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag), + '<%tag% name="%file%"> is invalid, file is not in <contents>'); + } + + function _cannotValidateNoPathSet() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Cannot validate files, no path to package file is set (use setPackageFile())'); + } + + function _usesroletaskMustHaveChannelOrUri($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain either <uri>, or <channel> and <package>'); + } + + function _usesroletaskMustHavePackage($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain <package>'); + } + + function _usesroletaskMustHaveRoleTask($tag, $type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type), + '<%tag%> must contain <%type%> defining the %type% to be used'); + } + + function _cannotConflictWithAllOs($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '%tag% cannot conflict with all OSes'); + } + + function _invalidDepGroupName($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Invalid dependency group name "%name%"'); + } + + function _multipleToplevelDirNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Multiple top-level <dir> tags are not allowed. Enclose them ' . + 'in a <dir name="/">'); + } + + function _multipleInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Only one <install> tag is allowed for file "%file%"'); + } + + function _ignoreAndInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Cannot have both <ignore> and <install> tags for file "%file%"'); + } + + function _analyzeBundledPackages() + { + if (!$this->_isValid) { + return false; + } + if (!$this->_pf->getPackageType() == 'bundle') { + return false; + } + if (!isset($this->_pf->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array($common, 'log'); + $info = $this->_pf->getContents(); + $info = $info['bundledpackage']; + if (!is_array($info)) { + $info = array($info); + } + $pkg = &new PEAR_PackageFile($this->_pf->_config); + foreach ($info as $package) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) { + $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package); + $this->_isValid = 0; + continue; + } + call_user_func_array($log, array(1, "Analyzing bundled package $package")); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package, + PEAR_VALIDATE_NORMAL); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + call_user_func_array($log, array(0, "ERROR: package $package is not a valid " . + 'package')); + $inf = $ret->getUserInfo(); + if (is_array($inf)) { + foreach ($inf as $err) { + call_user_func_array($log, array(1, $err['message'])); + } + } + return false; + } + } + return true; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_pf->_packageFile)) { + $this->_cannotValidateNoPathSet(); + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array(&$common, 'log'); + $info = $this->_pf->getContents(); + if (!$info || !isset($info['dir']['file'])) { + $this->_tagCannotBeEmpty('contents><dir'); + return false; + } + $info = $info['dir']['file']; + if (isset($info['attribs'])) { + $info = array($info); + } + $provides = array(); + foreach ($info as $fa) { + $fa = $fa['attribs']; + $file = $fa['name']; + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) { + $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file); + $this->_isValid = 0; + continue; + } + if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo)); + } + } + } + $this->_packageName = $pn = $this->_pf->getPackage(); + $pnl = strlen($pn); + foreach ($provides as $key => $what) { + if (isset($what['explicit']) || !$what) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } + } + return $this->_isValid; + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @param boolean whether to analyze $file as the file contents + * @return mixed + */ + function analyzeSourceCode($file, $string = false) + { + if (!function_exists("token_get_all")) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer'); + return false; + } + + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + + if ($string) { + $contents = $file; + } else { + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + } + + // Silence this function so we can catch PHP Warnings and show our own custom message + $tokens = @token_get_all($contents); + if (isset($php_errormsg)) { + if (isset($this->_stack)) { + $pn = $this->_pf->getPackage(); + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'package' => $pn), + 'in %file%: Could not process file for unkown reasons,' . + ' possibly a PHP parse error in %file% from %package%'); + } + } +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + ) + ) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array( + 'file' => $file), + 'Error, PHP5 token encountered in %file%,' . + ' analysis should be in PHP5'); + } else { + PEAR::raiseError('Error: PHP5 token encountered in ' . $file . + 'packaging should be done in PHP 5'); + return false; + } + } + } + + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + $token = $tokens[$i - 1][0]; + if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + + continue 2; + } + } + + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return array(); + } + + $providesret = array(); + $file = basename($srcinfo['source_file']); + $pn = isset($this->_pf) ? $this->_pf->getPackage() : ''; + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $providesret[$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($providesret[$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + + return $providesret; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/PackageFile/v2/rw.php b/includes/pear/PEAR/PackageFile/v2/rw.php new file mode 100644 index 0000000..4320d33 --- /dev/null +++ b/includes/pear/PEAR/PackageFile/v2/rw.php @@ -0,0 +1,1607 @@ +<?php +/** + * PEAR_PackageFile_v2, package.xml version 2.0, read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * For base class + */ +require_once 'PEAR/PackageFile/v2.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + */ +class PEAR_PackageFile_v2_rw extends PEAR_PackageFile_v2 +{ + /** + * @param string Extension name + * @return bool success of operation + */ + function setProvidesExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (!isset($this->_packageInfo['providesextension'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $extension, 'providesextension'); + } + $this->_packageInfo['providesextension'] = $extension; + return true; + } + return false; + } + + function setPackage($package) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.0 + http://pear.php.net/dtd/package-2.0.xsd', + )), $this->_packageInfo); + } + if (!isset($this->_packageInfo['name'])) { + return $this->_packageInfo = array_merge(array('name' => $package), + $this->_packageInfo); + } + $this->_packageInfo['name'] = $package; + } + + /** + * set this as a package.xml version 2.1 + * @access private + */ + function _setPackageVersion2_1() + { + $info = array( + 'version' => '2.1', + 'xmlns' => 'http://pear.php.net/dtd/package-2.1', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.1 + http://pear.php.net/dtd/package-2.1.xsd', + ); + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => $info), $this->_packageInfo); + } else { + $this->_packageInfo['attribs'] = $info; + } + } + + function setUri($uri) + { + unset($this->_packageInfo['channel']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['uri'])) { + // ensure that the uri tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $uri, 'uri'); + } + $this->_packageInfo['uri'] = $uri; + } + + function setChannel($channel) + { + unset($this->_packageInfo['uri']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['channel'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $channel, 'channel'); + } + $this->_packageInfo['channel'] = $channel; + } + + function setExtends($extends) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['extends'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $extends, 'extends'); + } + $this->_packageInfo['extends'] = $extends; + } + + function setSummary($summary) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['summary'])) { + // ensure that the summary tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $summary, 'summary'); + } + $this->_packageInfo['summary'] = $summary; + } + + function setDescription($desc) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['description'])) { + // ensure that the description tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $desc, 'description'); + } + $this->_packageInfo['description'] = $desc; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email, $active = 'yes') + { + if (!in_array($role, array('lead', 'developer', 'contributor', 'helper'))) { + return false; + } + if (isset($this->_packageInfo[$role])) { + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + $this->_packageInfo[$role][] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } else { + $testarr = array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'); + foreach (array('lead', 'developer', 'contributor', 'helper') as $testrole) { + array_shift($testarr); + if ($role == $testrole) { + break; + } + } + if (!isset($this->_packageInfo[$role])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, $testarr, + array(), $role); + } + $this->_packageInfo[$role] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } + $this->_isValid = 0; + } + + function updateMaintainer($newrole, $handle, $name, $email, $active = 'yes') + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + $info = $this->_packageInfo[$role]; + if (!isset($info[0])) { + if (isset($info['user']) && $info['user'] == $handle) { + $found = true; + break; + } + } + foreach ($info as $i => $maintainer) { + if (!isset($maintainer['user'])) { + continue; + } + if ($maintainer['user'] == $handle) { + $found = $i; + break 2; + } + } + } + if ($found === false) { + return $this->addMaintainer($newrole, $handle, $name, $email, $active); + } + if ($found !== false) { + if ($found === true) { + unset($this->_packageInfo[$role]); + } else { + unset($this->_packageInfo[$role][$found]); + $this->_packageInfo[$role] = array_values($this->_packageInfo[$role]); + } + } + $this->addMaintainer($newrole, $handle, $name, $email, $active); + $this->_isValid = 0; + } + + function deleteMaintainer($handle) + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + foreach ($this->_packageInfo[$role] as $i => $maintainer) { + if ($maintainer['user'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo[$role][$found]); + if (!count($this->_packageInfo[$role]) && $role == 'lead') { + $this->_isValid = 0; + } + if (!count($this->_packageInfo[$role])) { + unset($this->_packageInfo[$role]); + return true; + } + $this->_packageInfo[$role] = + array_values($this->_packageInfo[$role]); + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + return true; + } + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + } + return false; + } + + function setReleaseVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['release'])) { + unset($this->_packageInfo['version']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + function setAPIVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['api'])) { + unset($this->_packageInfo['version']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + /** + * snapshot|devel|alpha|beta|stable + */ + function setReleaseStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['release'])) { + unset($this->_packageInfo['stability']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + /** + * @param devel|alpha|beta|stable + */ + function setAPIStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['api'])) { + unset($this->_packageInfo['stability']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + function setLicense($license, $uri = false, $filesource = false) + { + if (!isset($this->_packageInfo['license'])) { + // ensure that the license tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), 0, 'license'); + } + if ($uri || $filesource) { + $attribs = array(); + if ($uri) { + $attribs['uri'] = $uri; + } + $uri = true; // for test below + if ($filesource) { + $attribs['filesource'] = $filesource; + } + } + $license = $uri ? array('attribs' => $attribs, '_content' => $license) : $license; + $this->_packageInfo['license'] = $license; + $this->_isValid = 0; + } + + function setNotes($notes) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['notes'])) { + // ensure that the notes tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $notes, 'notes'); + } + $this->_packageInfo['notes'] = $notes; + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Reset the listing of package contents + * @param string base installation dir for the whole package, if any + */ + function clearContents($baseinstall = false) + { + $this->_filesValid = false; + $this->_isValid = 0; + if (!isset($this->_packageInfo['contents'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if ($this->getPackageType() != 'bundle') { + $this->_packageInfo['contents'] = + array('dir' => array('attribs' => array('name' => '/'))); + if ($baseinstall) { + $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'] = $baseinstall; + } + } else { + $this->_packageInfo['contents'] = array('bundledpackage' => array()); + } + } + + /** + * @param string relative path of the bundled package. + */ + function addBundledPackage($path) + { + if ($this->getPackageType() != 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $path, array( + 'contents' => array('compatible', 'dependencies', 'providesextension', + 'usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + 'bundledpackage' => array())); + } + + /** + * @param string file name + * @param PEAR_Task_Common a read/write task + */ + function addTaskToFile($filename, $task) + { + if (!method_exists($task, 'getXml')) { + return false; + } + if (!method_exists($task, 'getName')) { + return false; + } + if (!method_exists($task, 'validate')) { + return false; + } + if (!$task->validate()) { + return false; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $this->getTasksNs(); // discover the tasks namespace if not done already + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $t = isset($this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? + $this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'][$i] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$i][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } else { + $t = isset($this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? $this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } + return true; + } + } + } + return false; + } + + /** + * @param string path to the file + * @param string filename + * @param array extra attributes + */ + function addFile($dir, $file, $attrs) + { + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $attrs['name'] = $dir . $file; + if (!isset($this->_packageInfo['contents'])) { + // ensure that the contents tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if (isset($this->_packageInfo['contents']['dir']['file'])) { + if (!isset($this->_packageInfo['contents']['dir']['file'][0])) { + $this->_packageInfo['contents']['dir']['file'] = + array($this->_packageInfo['contents']['dir']['file']); + } + $this->_packageInfo['contents']['dir']['file'][]['attribs'] = $attrs; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'] = $attrs; + } + } + + /** + * @param string Dependent package name + * @param string Dependent package's channel name + * @param string minimum version of specified package that this release is guaranteed to be + * compatible with + * @param string maximum version of specified package that this release is guaranteed to be + * compatible with + * @param string versions of specified package that this release is not compatible with + */ + function addCompatiblePackage($name, $channel, $min, $max, $exclude = false) + { + $this->_isValid = 0; + $set = array( + 'name' => $name, + 'channel' => $channel, + 'min' => $min, + 'max' => $max, + ); + if ($exclude) { + $set['exclude'] = $exclude; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'compatible' => array('dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the <usesrole> tag entirely + */ + function resetUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + unset($this->_packageInfo['usesrole']); + } + } + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsesrole($role, $packageOrUri, $channel = false) { + $set = array('role' => $role); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usesrole' => array('usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the <usestask> tag entirely + */ + function resetUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + unset($this->_packageInfo['usestask']); + } + } + + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsestask($task, $packageOrUri, $channel = false) { + $set = array('task' => $task); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usestask' => array('srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Remove all compatible tags + */ + function clearCompatible() + { + unset($this->_packageInfo['compatible']); + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + if (!isset($this->_packageInfo['dependencies'])) { + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'))); + } + $this->_packageInfo['dependencies'] = array(); + } + + /** + * @param string minimum PHP version allowed + * @param string maximum PHP version allowed + * @param array $exclude incompatible PHP versions + */ + function setPhpDep($min, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['php'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['php']), + 'warning: PHP dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['php']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'php' => array('pearinstaller', 'package', 'subpackage', 'extension', 'os', 'arch') + )); + return true; + } + + /** + * @param string minimum allowed PEAR installer version + * @param string maximum allowed PEAR installer version + * @param string recommended PEAR installer version + * @param array incompatible version of the PEAR installer + */ + function setPearinstallerDep($min, $max = false, $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['pearinstaller'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['pearinstaller']), + 'warning: PEAR Installer dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['pearinstaller']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'pearinstaller' => array('package', 'subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param array|false versions to exclude from installation + */ + function addConflictingPackageDepWithChannel($name, $channel, + $providesextension = false, $min = false, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, $channel, false, $min, $max, false, + $exclude, $providesextension, false, true); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + */ + function addConflictingPackageDepWithUri($name, $uri, $providesextension = false) + { + $this->_isValid = 0; + $dep = + array( + 'name' => $name, + 'uri' => $uri, + 'conflicts' => '', + ); + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + function addDependencyGroup($name, $hint) + { + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, + array('attribs' => array('name' => $name, 'hint' => $hint)), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'group' => array(), + )); + } + + /** + * @param string package name + * @param string|false channel name, false if this is a uri + * @param string|false uri name, false if this is a channel + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param string|false recommended installation version + * @param array|false versions to exclude from installation + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param bool if true, tells the installer to negate this dependency (conflicts) + * @return array + * @access private + */ + function _constructDep($name, $channel, $uri, $min, $max, $recommended, $exclude, + $providesextension = false, $nodefault = false, + $conflicts = false) + { + $dep = + array( + 'name' => $name, + ); + if ($channel) { + $dep['channel'] = $channel; + } elseif ($uri) { + $dep['uri'] = $uri; + } + if ($min) { + $dep['min'] = $min; + } + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($nodefault) { + $dep['nodefault'] = ''; + } + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + return $dep; + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array|false optional excluded versions + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()}, or a subpackage is added with + * a providesextension + */ + function addGroupPackageDepWithChannel($type, $groupname, $name, $channel, $min = false, + $max = false, $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package uri + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()} + */ + function addGroupPackageDepWithURI($type, $groupname, $name, $uri, $providesextension = false, + $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param string group name (must be pre-existing) + * @param string extension name + * @param string minimum version allowed + * @param string maximum version allowed + * @param string recommended version + * @param array incompatible versions + */ + function addGroupExtensionDep($groupname, $name, $min = false, $max = false, + $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + return $this->_addGroupDependency('extension', $dep, $groupname); + } + + /** + * @param package|subpackage|extension + * @param array dependency contents + * @param string name of the dependency group to add this to + * @return boolean + * @access private + */ + function _addGroupDependency($type, $dep, $groupname) + { + $arr = array('subpackage', 'extension'); + if ($type != 'package') { + array_shift($arr); + } + if ($type == 'extension') { + array_shift($arr); + } + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } else { + if (!isset($this->_packageInfo['dependencies']['group'][0])) { + if ($this->_packageInfo['dependencies']['group']['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } else { + return false; + } + } else { + foreach ($this->_packageInfo['dependencies']['group'] as $i => $group) { + if ($group['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'][$i] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'][$i], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } + } + return false; + } + } + } + + /** + * @param optional|required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param array|false optional excluded versions + */ + function addPackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if (!in_array($type, array('optional', 'required'), true)) { + $type = 'required'; + } + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required + * @param string name of the package + * @param string uri of the package + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addPackageDepWithUri($type, $name, $uri, $providesextension = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package uri for download + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithUri($type, $name, $uri, $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionDep($type, $name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'extension' => array('os', 'arch') + )); + } + + /** + * @param string Operating system name + * @param boolean true if this package cannot be installed on this OS + */ + function addOsDep($name, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'os' => array('arch') + )); + } + + /** + * @param string Architecture matching pattern + * @param boolean true if this package cannot be installed on this architecture + */ + function addArchDep($pattern, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'arch' => array() + )); + } + + /** + * Set the kind of package, and erase all release tags + * + * - a php package is a PEAR-style package + * - an extbin package is a PECL-style extension binary + * - an extsrc package is a PECL-style source for a binary + * - an zendextbin package is a PECL-style zend extension binary + * - an zendextsrc package is a PECL-style source for a zend extension binary + * - a bundle package is a collection of other pre-packaged packages + * @param php|extbin|extsrc|zendextsrc|zendextbin|bundle + * @return bool success + */ + function setPackageType($type) + { + $this->_isValid = 0; + if (!in_array($type, array('php', 'extbin', 'extsrc', 'zendextsrc', + 'zendextbin', 'bundle'))) { + return false; + } + + if (in_array($type, array('zendextsrc', 'zendextbin'))) { + $this->_setPackageVersion2_1(); + } + + if ($type != 'bundle') { + $type .= 'release'; + } + + foreach (array('phprelease', 'extbinrelease', 'extsrcrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle') as $test) { + unset($this->_packageInfo[$test]); + } + + if (!isset($this->_packageInfo[$type])) { + // ensure that the release tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('changelog'), + array(), $type); + } + + $this->_packageInfo[$type] = array(); + return true; + } + + /** + * @return bool true if package type is set up + */ + function addRelease() + { + if ($type = $this->getPackageType()) { + if ($type != 'bundle') { + $type .= 'release'; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array($type => array('changelog'))); + return true; + } + return false; + } + + /** + * Get the current release tag in order to add to it + * @param bool returns only releases that have installcondition if true + * @return array|null + */ + function &_getCurrentRelease($strict = true) + { + if ($p = $this->getPackageType()) { + if ($strict) { + if ($p == 'extsrc' || $p == 'zendextsrc') { + $a = null; + return $a; + } + } + if ($p != 'bundle') { + $p .= 'release'; + } + if (isset($this->_packageInfo[$p][0])) { + return $this->_packageInfo[$p][count($this->_packageInfo[$p]) - 1]; + } else { + return $this->_packageInfo[$p]; + } + } else { + $a = null; + return $a; + } + } + + /** + * Add a file to the current release that should be installed under a different name + * @param string <contents> path to file + * @param string name the file should be installed as + */ + function addInstallAs($path, $as) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path, 'as' => $as)), + array( + 'filelist' => array(), + 'install' => array('ignore') + )); + } + + /** + * Add a file to the current release that should be ignored + * @param string <contents> path to file + * @return bool success of operation + */ + function addIgnore($path) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path)), + array( + 'filelist' => array(), + 'ignore' => array() + )); + } + + /** + * Add an extension binary package for this extension source code release + * + * Note that the package must be from the same channel as the extension source package + * @param string + */ + function addBinarypackage($package) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, $package, + array( + 'binarypackage' => array('filelist'), + )); + } + + /** + * Add a configureoption to an extension source package + * @param string + * @param string + * @param string + */ + function addConfigureOption($name, $prompt, $default = null) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + + $opt = array('attribs' => array('name' => $name, 'prompt' => $prompt)); + if ($default !== null) { + $opt['attribs']['default'] = $default; + } + + $this->_isValid = 0; + $r = $this->_mergeTag($r, $opt, + array( + 'configureoption' => array('binarypackage', 'filelist'), + )); + } + + /** + * Set an installation condition based on php version for the current release set + * @param string minimum version + * @param string maximum version + * @param false|array incompatible versions of PHP + */ + function setPhpInstallCondition($min, $max, $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['php'])) { + unset($r['installconditions']['php']); + } + $dep = array('min' => $min, 'max' => $max); + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionInstallCondition($name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'extension' => array('os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'extension' => array('os', 'arch') + )); + } + } + + /** + * Set an installation condition based on operating system for the current release set + * @param string OS name + * @param bool whether this OS is incompatible with the current release + */ + function setOsInstallCondition($name, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['os'])) { + unset($r['installconditions']['os']); + } + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'os' => array('arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'os' => array('arch') + )); + } + } + + /** + * Set an installation condition based on architecture for the current release set + * @param string architecture pattern + * @param bool whether this arch is incompatible with the current release + */ + function setArchInstallCondition($pattern, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['arch'])) { + unset($r['installconditions']['arch']); + } + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'arch' => array() + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'arch' => array() + )); + } + } + + /** + * For extension binary releases, this is used to specify either the + * static URI to a source package, or the package name and channel of the extsrc/zendextsrc + * package it is based on. + * @param string Package name, or full URI to source package (extsrc/zendextsrc type) + */ + function setSourcePackage($packageOrUri) + { + $this->_isValid = 0; + if (isset($this->_packageInfo['channel'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $packageOrUri, 'srcpackage'); + } else { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), $packageOrUri, 'srcuri'); + } + } + + /** + * Generate a valid change log entry from the current package.xml + * @param string|false + */ + function generateChangeLogEntry($notes = false) + { + return array( + 'version' => + array( + 'release' => $this->getVersion('release'), + 'api' => $this->getVersion('api'), + ), + 'stability' => + $this->getStability(), + 'date' => $this->getDate(), + 'license' => $this->getLicense(true), + 'notes' => $notes ? $notes : $this->getNotes() + ); + } + + /** + * @param string release version to set change log notes for + * @param array output of {@link generateChangeLogEntry()} + */ + function setChangelogEntry($releaseversion, $contents) + { + if (!isset($this->_packageInfo['changelog'])) { + $this->_packageInfo['changelog']['release'] = $contents; + return; + } + if (!isset($this->_packageInfo['changelog']['release'][0])) { + if ($this->_packageInfo['changelog']['release']['version']['release'] == $releaseversion) { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + } else { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + return $this->_packageInfo['changelog']['release'][] = $contents; + } + } + foreach($this->_packageInfo['changelog']['release'] as $index => $changelog) { + if (isset($changelog['version']) && + strnatcasecmp($changelog['version']['release'], $releaseversion) == 0) { + $curlog = $index; + } + } + if (isset($curlog)) { + $this->_packageInfo['changelog']['release'][$curlog] = $contents; + } else { + $this->_packageInfo['changelog']['release'][] = $contents; + } + } + + /** + * Remove the changelog entirely + */ + function clearChangeLog() + { + unset($this->_packageInfo['changelog']); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Packager.php b/includes/pear/PEAR/Packager.php new file mode 100644 index 0000000..057a714 --- /dev/null +++ b/includes/pear/PEAR/Packager.php @@ -0,0 +1,201 @@ +<?php +/** + * PEAR_Packager for generating releases + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; +require_once 'System.php'; + +/** + * Administration class used to make a PEAR release tarball. + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Packager extends PEAR_Common +{ + /** + * @var PEAR_Registry + */ + var $_registry; + + function package($pkgfile = null, $compress = true, $pkg2 = null) + { + // {{{ validate supplied package.xml file + if (empty($pkgfile)) { + $pkgfile = 'package.xml'; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pkg = &new PEAR_PackageFile($this->config, $this->debug); + $pf = &$pkg->fromPackageFile($pkgfile, PEAR_VALIDATE_NORMAL); + $main = &$pf; + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); + } + } + + $this->log(0, $pf->getMessage()); + return $this->raiseError("Cannot package, errors in package file"); + } + + foreach ($pf->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + // }}} + if ($pkg2) { + $this->log(0, 'Attempting to process the second package file'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf2 = &$pkg->fromPackageFile($pkg2, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf2)) { + if (is_array($pf2->getUserInfo())) { + foreach ($pf2->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); + } + } + $this->log(0, $pf2->getMessage()); + return $this->raiseError("Cannot package, errors in second package file"); + } + + foreach ($pf2->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + if ($pf2->getPackagexmlVersion() == '2.0' || + $pf2->getPackagexmlVersion() == '2.1' + ) { + $main = &$pf2; + $other = &$pf; + } else { + $main = &$pf; + $other = &$pf2; + } + + if ($main->getPackagexmlVersion() != '2.0' && + $main->getPackagexmlVersion() != '2.1') { + return PEAR::raiseError('Error: cannot package two package.xml version 1.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); + } + + if ($other->getPackagexmlVersion() != '1.0') { + return PEAR::raiseError('Error: cannot package two package.xml version 2.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); + } + } + + $main->setLogger($this); + if (!$main->validate(PEAR_VALIDATE_PACKAGING)) { + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + return $this->raiseError("Cannot package, errors in package"); + } + + foreach ($main->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + if ($pkg2) { + $other->setLogger($this); + $a = false; + if (!$other->validate(PEAR_VALIDATE_NORMAL) || $a = !$main->isEquivalent($other)) { + foreach ($other->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + if ($a) { + return $this->raiseError('The two package.xml files are not equivalent!'); + } + + return $this->raiseError("Cannot package, errors in package"); + } + + foreach ($other->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + $gen = &$main->getDefaultGenerator(); + $tgzfile = $gen->toTgz2($this, $other, $compress); + if (PEAR::isError($tgzfile)) { + return $tgzfile; + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, 'Tag the released code with "pear cvstag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, 'Tag the released code with "pear svntag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } + } else { // this branch is executed for single packagefile packaging + $gen = &$pf->getDefaultGenerator(); + $tgzfile = $gen->toTgz($this, $compress); + if (PEAR::isError($tgzfile)) { + $this->log(0, $tgzfile->getMessage()); + return $this->raiseError("Cannot package, errors in package"); + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, "Tag the released code with `pear cvstag $pkgfile'"); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, "Tag the released code with `pear svntag $pkgfile'"); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } + } + + return $dest_package; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/REST.php b/includes/pear/PEAR/REST.php new file mode 100644 index 0000000..76a13cd --- /dev/null +++ b/includes/pear/PEAR/REST.php @@ -0,0 +1,483 @@ +<?php +/** + * PEAR_REST + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * For downloading xml files + */ +require_once 'PEAR.php'; +require_once 'PEAR/XMLParser.php'; + +/** + * Intelligently retrieve data, following hyperlinks if necessary, and re-directing + * as well + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_REST +{ + var $config; + var $_options; + + function PEAR_REST(&$config, $options = array()) + { + $this->config = &$config; + $this->_options = $options; + } + + /** + * Retrieve REST data, but always retrieve the local cache if it is available. + * + * This is useful for elements that should never change, such as information on a particular + * release + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (file_exists($cachefile)) { + return unserialize(implode('', file($cachefile))); + } + + return $this->retrieveData($url, $accept, $forcestring, $channel); + } + + /** + * Retrieve a remote REST resource + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveData($url, $accept = false, $forcestring = false, $channel = false) + { + $cacheId = $this->getCacheId($url); + if ($ret = $this->useLocalCache($url, $cacheId)) { + return $ret; + } + + $file = $trieddownload = false; + if (!isset($this->_options['offline'])) { + $trieddownload = true; + $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel); + } + + if (PEAR::isError($file)) { + if ($file->getCode() !== -9276) { + return $file; + } + + $trieddownload = false; + $file = false; // use local copy if available on socket connect error + } + + if (!$file) { + $ret = $this->getCache($url); + if (!PEAR::isError($ret) && $trieddownload) { + // reset the age of the cache if the server says it was unmodified + $result = $this->saveCache($url, $ret, null, true, $cacheId); + if (PEAR::isError($result)) { + return PEAR::raiseError($result->getMessage()); + } + } + + return $ret; + } + + if (is_array($file)) { + $headers = $file[2]; + $lastmodified = $file[1]; + $content = $file[0]; + } else { + $headers = array(); + $lastmodified = false; + $content = $file; + } + + if ($forcestring) { + $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId); + if (PEAR::isError($result)) { + return PEAR::raiseError($result->getMessage()); + } + + return $content; + } + + if (isset($headers['content-type'])) { + switch ($headers['content-type']) { + case 'text/xml' : + case 'application/xml' : + case 'text/plain' : + if ($headers['content-type'] === 'text/plain') { + $check = substr($content, 0, 5); + if ($check !== '<?xml') { + break; + } + } + + $parser = new PEAR_XMLParser; + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $parser->parse($content); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' . + $err->getMessage()); + } + $content = $parser->getData(); + case 'text/html' : + default : + // use it as a string + } + } else { + // assume XML + $parser = new PEAR_XMLParser; + $parser->parse($content); + $content = $parser->getData(); + } + + $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId); + if (PEAR::isError($result)) { + return PEAR::raiseError($result->getMessage()); + } + + return $content; + } + + function useLocalCache($url, $cacheid = null) + { + if ($cacheid === null) { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + if (!file_exists($cacheidfile)) { + return false; + } + + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $cachettl = $this->config->get('cache_ttl'); + // If cache is newer than $cachettl seconds, we use the cache! + if (time() - $cacheid['age'] < $cachettl) { + return $this->getCache($url); + } + + return false; + } + + function getCacheId($url) + { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + + if (!file_exists($cacheidfile)) { + return false; + } + + $ret = unserialize(implode('', file($cacheidfile))); + return $ret; + } + + function getCache($url) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (!file_exists($cachefile)) { + return PEAR::raiseError('No cached content available for "' . $url . '"'); + } + + return unserialize(implode('', file($cachefile))); + } + + /** + * @param string full URL to REST resource + * @param string original contents of the REST resource + * @param array HTTP Last-Modified and ETag headers + * @param bool if true, then the cache id file should be regenerated to + * trigger a new time-to-live value + */ + function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null) + { + $cache_dir = $this->config->get('cache_dir'); + $d = $cache_dir . DIRECTORY_SEPARATOR . md5($url); + $cacheidfile = $d . 'rest.cacheid'; + $cachefile = $d . 'rest.cachefile'; + + if (!is_dir($cache_dir)) { + if (System::mkdir(array('-p', $cache_dir)) === false) { + return PEAR::raiseError("The value of config option cache_dir ($cache_dir) is not a directory and attempts to create the directory failed."); + } + } + + if ($cacheid === null && $nochange) { + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $idData = serialize(array( + 'age' => time(), + 'lastChange' => ($nochange ? $cacheid['lastChange'] : $lastmodified), + )); + + $result = $this->saveCacheFile($cacheidfile, $idData); + if (PEAR::isError($result)) { + return $result; + } elseif ($nochange) { + return true; + } + + $result = $this->saveCacheFile($cachefile, serialize($contents)); + if (PEAR::isError($result)) { + if (file_exists($cacheidfile)) { + @unlink($cacheidfile); + } + + return $result; + } + + return true; + } + + function saveCacheFile($file, $contents) + { + $len = strlen($contents); + + $cachefile_fp = @fopen($file, 'xb'); // x is the O_CREAT|O_EXCL mode + if ($cachefile_fp !== false) { // create file + if (fwrite($cachefile_fp, $contents, $len) < $len) { + fclose($cachefile_fp); + return PEAR::raiseError("Could not write $file."); + } + } else { // update file + $cachefile_lstat = lstat($file); + $cachefile_fp = @fopen($file, 'wb'); + if (!$cachefile_fp) { + return PEAR::raiseError("Could not open $file for writing."); + } + + $cachefile_fstat = fstat($cachefile_fp); + if ( + $cachefile_lstat['mode'] == $cachefile_fstat['mode'] && + $cachefile_lstat['ino'] == $cachefile_fstat['ino'] && + $cachefile_lstat['dev'] == $cachefile_fstat['dev'] && + $cachefile_fstat['nlink'] === 1 + ) { + if (fwrite($cachefile_fp, $contents, $len) < $len) { + fclose($cachefile_fp); + return PEAR::raiseError("Could not write $file."); + } + } else { + fclose($cachefile_fp); + $link = function_exists('readlink') ? readlink($file) : $file; + return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $file . ' as it is symlinked to ' . $link . ' - Possible symlink attack'); + } + } + + fclose($cachefile_fp); + return true; + } + + /** + * Efficiently Download a file through HTTP. Returns downloaded file as a string in-memory + * This is best used for small files + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param string $save_dir directory to save file in + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @return string|array Returns the contents of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($this->config->get('http_proxy')&& + $proxy = parse_url($this->config->get('http_proxy')) + ) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if ($schema === 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http'; + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + if (isset($proxy['host'])) { + $request = "GET $url HTTP/1.1\r\n"; + } else { + $request = "GET $path HTTP/1.1\r\n"; + } + + $request .= "Host: $host\r\n"; + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n"; + + $username = $this->config->get('username', null, $channel); + $password = $this->config->get('password', null, $channel); + + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Accept-Encoding:\r\n"; + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15); + if (!$fp) { + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276); + } + } else { + if ($schema === 'https') { + $host = 'ssl://' . $host; + } + + $fp = @fsockopen($host, $port, $errno, $errstr); + if (!$fp) { + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + } + + fwrite($fp, $request); + + $headers = array(); + $reply = 0; + while ($line = trim(fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $data = ''; + while ($chunk = @fread($fp, 8192)) { + $data .= $chunk; + } + fclose($fp); + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + + return array($data, $lastmodified, $headers); + } + + return $data; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/REST/10.php b/includes/pear/PEAR/REST/10.php new file mode 100644 index 0000000..7540efa --- /dev/null +++ b/includes/pear/PEAR/REST/10.php @@ -0,0 +1,871 @@ +<?php +/** + * PEAR_REST_10 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.0 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_10 +{ + /** + * @var PEAR_REST + */ + var $_rest; + function PEAR_REST_10($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + * <pre> + * array( + * 'package' => 'packagename', + * 'channel' => 'channelname', + * ['state' => 'alpha' (or valid state),] + * -or- + * ['version' => '1.whatever'] + * </pre> + * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + $found = true; + break; + } + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + $found = true; + break; + } + } + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + $recommended = $release['v']; + break; + } + } + } + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } else { + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + } + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + if (in_array($release['s'], $states)) { // if in the preferred state... + $found = true; // ... then use it + break; + } + } + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + /** + * Take raw data and return the array needed for processing a download URL + * + * @param string $base REST base uri + * @param string $package Package name + * @param array $release an array of format array('v' => version, 's' => state) + * describing the release to download + * @param array $info list of all releases as defined by allreleases.xml + * @param bool|null $found determines whether the release was found or this is the next + * best alternative. If null, then versions were skipped because + * of PHP dependency + * @return array|PEAR_Error + * @access private + */ + function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false) + { + if (!$found) { + $release = $info['r'][0]; + } + + $packageLower = strtolower($package); + $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' . + 'info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + return PEAR::raiseError('Package "' . $package . + '" does not have REST info xml available'); + } + + $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($releaseinfo)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST xml available'); + } + + $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + 'deps.' . $release['v'] . '.txt', false, true, $channel); + if (PEAR::isError($packagexml)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST dependency information available'); + } + + $packagexml = unserialize($packagexml); + if (!$packagexml) { + $packagexml = array(); + } + + $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower . + '/allreleases.xml', false, false, $channel); + if (PEAR::isError($allinfo)) { + return $allinfo; + } + + if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) { + $allinfo['r'] = array($allinfo['r']); + } + + $compatible = false; + foreach ($allinfo['r'] as $release) { + if ($release['v'] != $releaseinfo['v']) { + continue; + } + + if (!isset($release['co'])) { + break; + } + + $compatible = array(); + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + $comp = array(); + $comp['name'] = $entry['p']; + $comp['channel'] = $entry['c']; + $comp['min'] = $entry['min']; + $comp['max'] = $entry['max']; + if (isset($entry['x']) && !is_array($entry['x'])) { + $comp['exclude'] = $entry['x']; + } + + $compatible[] = $comp; + } + + if (count($compatible) == 1) { + $compatible = $compatible[0]; + } + + break; + } + + $deprecated = false; + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } + + $return = array( + 'version' => $releaseinfo['v'], + 'info' => $packagexml, + 'package' => $releaseinfo['p']['_content'], + 'stability' => $releaseinfo['st'], + 'compatible' => $compatible, + 'deprecated' => $deprecated, + ); + + if ($found) { + $return['url'] = $releaseinfo['g']; + return $return; + } + + $return['php'] = $phpversion; + return $return; + } + + function listPackages($base, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + return $packagelist['p']; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categories = array(); + + // c/categories.xml does not exist; + // check for every package its category manually + // This is SLOOOWWWW : /// + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + $ret = array(); + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist['p'] as $package) { + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + $cat = $inf['ca']['_content']; + if (!isset($categories[$cat])) { + $categories[$cat] = $inf['ca']; + } + } + + return array_values($categories); + } + + /** + * List a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + + if ($info == true) { + // get individual package info + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist as $i => $packageitem) { + $url = sprintf('%s'.'r/%s/latest.txt', + $base, + strtolower($packageitem['_content'])); + $version = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($version)) { + break; // skipit + } + $url = sprintf('%s'.'r/%s/%s.xml', + $base, + strtolower($packageitem['_content']), + $version); + $info = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($info)) { + break; // skipit + } + $packagelist[$i]['info'] = $info; + } + PEAR::popErrorHandling(); + } + + return $packagelist; + } + + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if ($this->_rest->config->get('verbose') > 0) { + $ui = &PEAR_Frontend::singleton(); + $ui->log('Retrieving data...0%', true); + } + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + // only search-packagename = quicksearch ! + if ($searchpackage && (!$searchsummary || empty($searchpackage))) { + $newpackagelist = array(); + foreach ($packagelist['p'] as $package) { + if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) { + $newpackagelist[] = $package; + } + } + $packagelist['p'] = $newpackagelist; + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $next = .1; + foreach ($packagelist['p'] as $progress => $package) { + if ($this->_rest->config->get('verbose') > 0) { + if ($progress / count($packagelist['p']) >= $next) { + if ($next == .5) { + $ui->log('50%', false); + } else { + $ui->log('.', false); + } + $next += .1; + } + } + + if ($basic) { // remote-list command + if ($dostable) { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/stable.txt', false, false, $channel); + } else { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/latest.txt', false, false, $channel); + } + if (PEAR::isError($latest)) { + $latest = false; + } + $info = array('stable' => $latest); + } else { // list-all command + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + if ($searchpackage) { + $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false); + if (!$found && !(isset($searchsummary) && !empty($searchsummary) + && (stristr($inf['s'], $searchsummary) !== false + || stristr($inf['d'], $searchsummary) !== false))) + { + continue; + }; + } + $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (PEAR::isError($releases)) { + continue; + } + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + unset($latest); + unset($unstable); + unset($stable); + unset($state); + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + if (!isset($unstable) && $release['s'] != 'stable') { + $latest = $unstable = $release['v']; + $state = $release['s']; + } + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + $deps = array(); + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + if (!isset($latest)) { + $latest = false; + } + if ($latest) { + $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $latest . '.txt', false, false, $channel); + if (!PEAR::isError($d)) { + $d = unserialize($d); + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + $deps[] = $dep; + } + } + } + } + if (!isset($stable)) { + $stable = '-n/a-'; + } + if (!$searchpackage) { + $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } else { + $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } + } + $ret[$package] = $info; + } + PEAR::popErrorHandling(); + return $ret; + } + + function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + foreach ($packagelist['p'] as $package) { + if (!isset($installed[strtolower($package)])) { + continue; + } + + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($info)) { + continue; // no remote releases + } + + if (!isset($info['r'])) { + continue; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + // $info['r'] is sorted by version number + usort($info['r'], array($this, '_sortReleasesByVersionNumber')); + foreach ($info['r'] as $release) { + if ($inst_version && version_compare($release['v'], $inst_version, '<=')) { + // not newer than the one installed + break; + } + + // new version > installed version + if (!$pref_state) { + // every state is a good state + $found = true; + break; + } else { + $new_state = $release['s']; + // if new state >= installed state: go + if (in_array($new_state, $this->betterStates($inst_state, true))) { + $found = true; + break; + } else { + // only allow to lower the state of package, + // if new state >= preferred state: go + if (in_array($new_state, $this->betterStates($pref_state, true))) { + $found = true; + break; + } + } + } + } + + if (!$found) { + continue; + } + + $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($relinfo)) { + return $relinfo; + } + + $ret[$package] = array( + 'version' => $release['v'], + 'state' => $release['s'], + 'filesize' => $relinfo['f'], + ); + } + + return $ret; + } + + function packageInfo($base, $package, $channel = false) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' . + $pinfo->getMessage()); + } + + $releases = array(); + $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (!PEAR::isError($allreleases)) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) { + $allreleases['r'] = array($allreleases['r']); + } + + $pf = new PEAR_PackageFile_v2; + foreach ($allreleases['r'] as $release) { + $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $release['v'] . '.txt', false, false, $channel); + if (PEAR::isError($ds)) { + continue; + } + + if (!isset($latest)) { + $latest = $release['v']; + } + + $pf->setDeps(unserialize($ds)); + $ds = $pf->getDeps(); + $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) + . '/' . $release['v'] . '.xml', false, false, $channel); + + if (PEAR::isError($info)) { + continue; + } + + $releases[$release['v']] = array( + 'doneby' => $info['m'], + 'license' => $info['l'], + 'summary' => $info['s'], + 'description' => $info['d'], + 'releasedate' => $info['da'], + 'releasenotes' => $info['n'], + 'state' => $release['s'], + 'deps' => $ds ? $ds : array(), + ); + } + } else { + $latest = ''; + } + + PEAR::popErrorHandling(); + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } else { + $deprecated = false; + } + + if (!isset($latest)) { + $latest = ''; + } + + return array( + 'name' => $pinfo['n'], + 'channel' => $pinfo['c'], + 'category' => $pinfo['ca']['_content'], + 'stable' => $latest, + 'license' => $pinfo['l'], + 'summary' => $pinfo['s'], + 'description' => $pinfo['d'], + 'releases' => $releases, + 'deprecated' => $deprecated, + ); + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + + if ($include) { + $i--; + } + + return array_slice($states, $i + 1); + } + + /** + * Sort releases by version number + * + * @access private + */ + function _sortReleasesByVersionNumber($a, $b) + { + if (version_compare($a['v'], $b['v'], '=')) { + return 0; + } + + if (version_compare($a['v'], $b['v'], '>')) { + return -1; + } + + if (version_compare($a['v'], $b['v'], '<')) { + return 1; + } + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/REST/11.php b/includes/pear/PEAR/REST/11.php new file mode 100644 index 0000000..6d2fcd8 --- /dev/null +++ b/includes/pear/PEAR/REST/11.php @@ -0,0 +1,341 @@ +<?php +/** + * PEAR_REST_11 - implement faster list-all/remote-list command + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.3 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.1 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.3 + */ +class PEAR_REST_11 +{ + /** + * @var PEAR_REST + */ + var $_rest; + + function PEAR_REST_11($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + $ret = array(); + if (!is_array($categorylist['c']) || !isset($categorylist['c'][0])) { + $categorylist['c'] = array($categorylist['c']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($categorylist['c'] as $progress => $category) { + $category = $category['_content']; + $packagesinfo = $this->_rest->retrieveData($base . + 'c/' . urlencode($category) . '/packagesinfo.xml', false, false, $channel); + + if (PEAR::isError($packagesinfo)) { + continue; + } + + if (!is_array($packagesinfo) || !isset($packagesinfo['pi'])) { + continue; + } + + if (!is_array($packagesinfo['pi']) || !isset($packagesinfo['pi'][0])) { + $packagesinfo['pi'] = array($packagesinfo['pi']); + } + + foreach ($packagesinfo['pi'] as $packageinfo) { + if (empty($packageinfo)) { + continue; + } + + $info = $packageinfo['p']; + $package = $info['n']; + $releases = isset($packageinfo['a']) ? $packageinfo['a'] : false; + unset($latest); + unset($unstable); + unset($stable); + unset($state); + + if ($releases) { + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + + if (!isset($unstable) && $release['s'] != 'stable') { + $unstable = $release['v']; + $state = $release['s']; + } + + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + } + + if ($basic) { // remote-list command + if (!isset($latest)) { + $latest = false; + } + + if ($dostable) { + // $state is not set if there are no releases + if (isset($state) && $state == 'stable') { + $ret[$package] = array('stable' => $latest); + } else { + $ret[$package] = array('stable' => '-n/a-'); + } + } else { + $ret[$package] = array('stable' => $latest); + } + + continue; + } + + // list-all command + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + + if (!isset($latest)) { + $latest = false; + } + + $deps = array(); + if ($latest && isset($packageinfo['deps'])) { + if (!is_array($packageinfo['deps']) || + !isset($packageinfo['deps'][0]) + ) { + $packageinfo['deps'] = array($packageinfo['deps']); + } + + $d = false; + foreach ($packageinfo['deps'] as $dep) { + if ($dep['v'] == $latest) { + $d = unserialize($dep['d']); + } + } + + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + + $deps[] = $dep; + } + } + } + + $info = array( + 'stable' => $latest, + 'summary' => $info['s'], + 'description' => $info['d'], + 'deps' => $deps, + 'category' => $info['ca']['_content'], + 'unstable' => $unstable, + 'state' => $state + ); + $ret[$package] = $info; + } + } + + PEAR::popErrorHandling(); + return $ret; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + if (!is_array($categorylist) || !isset($categorylist['c'])) { + return array(); + } + + if (isset($categorylist['c']['_content'])) { + // only 1 category + $categorylist['c'] = array($categorylist['c']); + } + + return $categorylist['c']; + } + + /** + * List packages in a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + if ($info == false) { + $url = '%s'.'c/%s/packages.xml'; + } else { + $url = '%s'.'c/%s/packagesinfo.xml'; + } + $url = sprintf($url, + $base, + urlencode($category)); + + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if (!is_array($packagelist)) { + return array(); + } + + if ($info == false) { + if (!isset($packagelist['p'])) { + return array(); + } + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + return $packagelist; + } + + // info == true + if (!isset($packagelist['pi'])) { + return array(); + } + + if (!is_array($packagelist['pi']) || + !isset($packagelist['pi'][0])) { // only 1 pkg + $packagelist_pre = array($packagelist['pi']); + } else { + $packagelist_pre = $packagelist['pi']; + } + + $packagelist = array(); + foreach ($packagelist_pre as $i => $item) { + // compatibility with r/<latest.txt>.xml + if (isset($item['a']['r'][0])) { + // multiple releases + $item['p']['v'] = $item['a']['r'][0]['v']; + $item['p']['st'] = $item['a']['r'][0]['s']; + } elseif (isset($item['a'])) { + // first and only release + $item['p']['v'] = $item['a']['r']['v']; + $item['p']['st'] = $item['a']['r']['s']; + } + + $packagelist[$i] = array('attribs' => $item['p']['r'], + '_content' => $item['p']['n'], + 'info' => $item['p']); + } + + return $packagelist; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/REST/13.php b/includes/pear/PEAR/REST/13.php new file mode 100644 index 0000000..f38cb62 --- /dev/null +++ b/includes/pear/PEAR/REST/13.php @@ -0,0 +1,299 @@ +<?php +/** + * PEAR_REST_13 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; +require_once 'PEAR/REST/10.php'; + +/** + * Implement REST 1.3 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_13 extends PEAR_REST_10 +{ + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * This is smart enough to resolve the minimum PHP version dependency prior to download + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + * <pre> + * array( + * 'package' => 'packagename', + * 'channel' => 'channelname', + * ['state' => 'alpha' (or valid state),] + * -or- + * ['version' => '1.whatever'] + * </pre> + * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + $skippedphp = false; + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + if (!isset($this->_rest->_options['force']) && + !isset($version) && + version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) .'/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + $pinfo['package'] = $dependency['name']; + $pinfo['channel'] = 'pear.php.net'; // this is always true - don't change this + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $pinfo['package'] = $dependency['name']; + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + + $skippedphp = $found = $release = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $recommended = $release['v']; + break; + } + } + } + + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } + + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + + if (in_array($release['s'], $states)) { // if in the preferred state... + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $found = true; // ... then use it + break; + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/REST/14.php b/includes/pear/PEAR/REST/14.php new file mode 100644 index 0000000..3358a46 --- /dev/null +++ b/includes/pear/PEAR/REST/14.php @@ -0,0 +1,120 @@ +<?php +/** + * PEAR_REST_14 + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Helgi Þormar Þorbjörnsson <helgi@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.9 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; +require_once 'PEAR/REST/13.php'; + +/** + * Implement REST 1.4 + * + * @category pear + * @package PEAR + * @author Helgi Þormar Þorbjörnsson <helgi@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.9 + */ +class PEAR_REST_14 extends PEAR_REST_13 +{ + function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg) + { + $packagelist = $this->_rest->retrieveData($base . 'p/latestpackages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + + if (isset($packagelist['p']['n'])) { + $packagelist['p'] = array($packagelist['p']); + } + + foreach ($packagelist['p'] as $package) { + if (!isset($installed[strtolower($package['n'])])) { + continue; + } + + $inst_version = $reg->packageInfo($package['n'], 'version', $channel); + $inst_state = $reg->packageInfo($package['n'], 'release_state', $channel); + + + $release = $found = false; + $data = array(); + if (isset($package['alpha'])) { + $data['alpha'] = $package['alpha']; + } + + if (isset($package['beta'])) { + $data['beta'] = $package['beta']; + } + + if (isset($package['stable'])) { + $data['stable'] = $package['stable']; + } + + foreach ($data as $state => $release) { + if ($inst_version && version_compare($release['v'], $inst_version, '<=')) { + // not newer than the one installed + break; + } + + // new version > installed version + if (!$pref_state) { + // every state is a good state + $found = true; + $release['state'] = $state; + break; + } else { + $new_state = $state; + // if new state >= installed state: go + if (in_array($new_state, $this->betterStates($inst_state, true))) { + $found = true; + $release['state'] = $state; + break; + } else { + // only allow to lower the state of package, + // if new state >= preferred state: go + if (in_array($new_state, $this->betterStates($pref_state, true))) { + $found = true; + $release['state'] = $state; + break; + } + } + } + } + + if (!$found) { + continue; + } + + $ret[$package] = array( + 'version' => $release['v'], + 'state' => $release['s'], + 'filesize' => $release['f'], + ); + } + + return $ret; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Registry.php b/includes/pear/PEAR/Registry.php new file mode 100644 index 0000000..02168db --- /dev/null +++ b/includes/pear/PEAR/Registry.php @@ -0,0 +1,2339 @@ +<?php +/** + * PEAR_Registry + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * for PEAR_Error + */ +require_once 'PEAR.php'; +require_once 'PEAR/DependencyDB.php'; + +define('PEAR_REGISTRY_ERROR_LOCK', -2); +define('PEAR_REGISTRY_ERROR_FORMAT', -3); +define('PEAR_REGISTRY_ERROR_FILE', -4); +define('PEAR_REGISTRY_ERROR_CONFLICT', -5); +define('PEAR_REGISTRY_ERROR_CHANNEL_FILE', -6); + +/** + * Administration class used to maintain the installed package database. + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V. V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Registry extends PEAR +{ + /** + * File containing all channel information. + * @var string + */ + var $channels = ''; + + /** Directory where registry files are stored. + * @var string + */ + var $statedir = ''; + + /** File where the file map is stored + * @var string + */ + var $filemap = ''; + + /** Directory where registry files for channels are stored. + * @var string + */ + var $channelsdir = ''; + + /** Name of file used for locking the registry + * @var string + */ + var $lockfile = ''; + + /** File descriptor used during locking + * @var resource + */ + var $lock_fp = null; + + /** Mode used during locking + * @var int + */ + var $lock_mode = 0; // XXX UNUSED + + /** Cache of package information. Structure: + * array( + * 'package' => array('id' => ... ), + * ... ) + * @var array + */ + var $pkginfo_cache = array(); + + /** Cache of file map. Structure: + * array( '/path/to/file' => 'package', ... ) + * @var array + */ + var $filemap_cache = array(); + + /** + * @var false|PEAR_ChannelFile + */ + var $_pearChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_peclChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_docChannel; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencyDB; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * PEAR_Registry constructor. + * + * @param string (optional) PEAR install directory (for .php files) + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PEAR channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PECL channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * + * @access public + */ + function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false, + $pecl_channel = false) + { + parent::PEAR(); + $this->setInstallDir($pear_install_dir); + $this->_pearChannel = $pear_channel; + $this->_peclChannel = $pecl_channel; + $this->_config = false; + } + + function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR) + { + $ds = DIRECTORY_SEPARATOR; + $this->install_dir = $pear_install_dir; + $this->channelsdir = $pear_install_dir.$ds.'.channels'; + $this->statedir = $pear_install_dir.$ds.'.registry'; + $this->filemap = $pear_install_dir.$ds.'.filemap'; + $this->lockfile = $pear_install_dir.$ds.'.lock'; + } + + function hasWriteAccess() + { + if (!file_exists($this->install_dir)) { + $dir = $this->install_dir; + while ($dir && $dir != '.') { + $olddir = $dir; + $dir = dirname($dir); + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + + if ($dir == $olddir) { // this can happen in safe mode + return @is_writable($dir); + } + } + + return false; + } + + return is_writeable($this->install_dir); + } + + function setConfig(&$config, $resetInstallDir = true) + { + $this->_config = &$config; + if ($resetInstallDir) { + $this->setInstallDir($config->get('php_dir')); + } + } + + function _initializeChannelDirs() + { + static $running = false; + if (!$running) { + $running = true; + $ds = DIRECTORY_SEPARATOR; + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $pear_channel = $this->_pearChannel; + if (!is_a($pear_channel, 'PEAR_ChannelFile') || !$pear_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setAlias('pear'); + $pear_channel->setServer('pear.php.net'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + //$pear_channel->setBaseURL('REST1.4', 'http://pear.php.net/rest/'); + } else { + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + } + + $pear_channel->validate(); + $this->_addChannel($pear_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'pecl.php.net.reg')) { + $pecl_channel = $this->_peclChannel; + if (!is_a($pecl_channel, 'PEAR_ChannelFile') || !$pecl_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pecl_channel = new PEAR_ChannelFile; + $pecl_channel->setAlias('pecl'); + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setSummary('PHP Extension Community Library'); + $pecl_channel->setDefaultPEARProtocols(); + $pecl_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pecl_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pecl_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + } else { + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setAlias('pecl'); + } + + $pecl_channel->validate(); + $this->_addChannel($pecl_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'doc.php.net.reg')) { + $doc_channel = $this->_docChannel; + if (!is_a($doc_channel, 'PEAR_ChannelFile') || !$doc_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setAlias('phpdocs'); + $doc_channel->setServer('doc.php.net'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + } else { + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('doc'); + } + + $doc_channel->validate(); + $this->_addChannel($doc_channel); + } + + if (!file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + $this->_addChannel($private); + } + $this->_rebuildFileMap(); + } + + $running = false; + } + } + + function _initializeDirs() + { + $ds = DIRECTORY_SEPARATOR; + // XXX Compatibility code should be removed in the future + // rename all registry files if any to lowercase + if (!OS_WINDOWS && file_exists($this->statedir) && is_dir($this->statedir) && + $handle = opendir($this->statedir)) { + $dest = $this->statedir . $ds; + while (false !== ($file = readdir($handle))) { + if (preg_match('/^.*[A-Z].*\.reg\\z/', $file)) { + rename($dest . $file, $dest . strtolower($file)); + } + } + closedir($handle); + } + + $this->_initializeChannelDirs(); + if (!file_exists($this->filemap)) { + $this->_rebuildFileMap(); + } + $this->_initializeDepDB(); + } + + function _initializeDepDB() + { + if (!isset($this->_dependencyDB)) { + static $initializing = false; + if (!$initializing) { + $initializing = true; + if (!$this->_config) { // never used? + $file = OS_WINDOWS ? 'pear.ini' : '.pearrc'; + $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR . + $file); + $this->_config->setRegistry($this); + $this->_config->set('php_dir', $this->install_dir); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + // attempt to recover by removing the dep db + if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb')) { + @unlink($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + echo $this->_dependencyDB->getMessage(); + echo 'Unrecoverable error'; + exit(1); + } + } + + $initializing = false; + } + } + } + + /** + * PEAR_Registry destructor. Makes sure no locks are forgotten. + * + * @access private + */ + function _PEAR_Registry() + { + parent::_PEAR(); + if (is_resource($this->lock_fp)) { + $this->_unlock(); + } + } + + /** + * Make sure the directory where we keep registry files exists. + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertStateDir($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_assertChannelStateDir($channel); + } + + static $init = false; + if (!file_exists($this->statedir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->statedir))) { + return $this->raiseError("could not create directory '{$this->statedir}'"); + } + $init = true; + } elseif (!is_dir($this->statedir)) { + return $this->raiseError('Cannot create directory ' . $this->statedir . ', ' . + 'it already exists and is not a directory'); + } + + $ds = DIRECTORY_SEPARATOR; + if (!file_exists($this->channelsdir)) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + $init = true; + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError('Cannot create directory ' . $this->channelsdir . ', ' . + 'it already exists and is not a directory'); + } + + if ($init) { + static $running = false; + if (!$running) { + $running = true; + $this->_initializeDirs(); + $running = false; + $init = false; + } + } else { + $this->_initializeDepDB(); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files exists for a non-standard channel. + * + * @param string channel name + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelStateDir($channel) + { + $ds = DIRECTORY_SEPARATOR; + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + return $this->_assertStateDir($channel); + } + + $channelDir = $this->_channelDirectoryName($channel); + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + + if (!file_exists($channelDir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $channelDir))) { + return $this->raiseError("could not create directory '" . $channelDir . + "'"); + } + } elseif (!is_dir($channelDir)) { + return $this->raiseError("could not create directory '" . $channelDir . + "', already exists and is not a directory"); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files for channels exists + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelDir() + { + if (!file_exists($this->channelsdir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir))) { + return $this->raiseError("could not create directory '{$this->channelsdir}'"); + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "', it already exists and is not a directory"); + } + + if (!file_exists($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir . DIRECTORY_SEPARATOR . '.alias'))) { + return $this->raiseError("could not create directory '{$this->channelsdir}/.alias'"); + } + } elseif (!is_dir($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "/.alias', it already exists and is not a directory"); + } + + return true; + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _packageFileName($package, $channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_channelDirectoryName($channel) . DIRECTORY_SEPARATOR . + strtolower($package) . '.reg'; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg'; + } + + /** + * Get the name of the file where data for a given channel is stored. + * @param string channel name + * @return string registry file name + */ + function _channelFileName($channel, $noaliases = false) + { + if (!$noaliases) { + if (file_exists($this->_getChannelAliasFileName($channel))) { + $channel = implode('', file($this->_getChannelAliasFileName($channel))); + } + } + return $this->channelsdir . DIRECTORY_SEPARATOR . str_replace('/', '_', + strtolower($channel)) . '.reg'; + } + + /** + * @param string + * @return string + */ + function _getChannelAliasFileName($alias) + { + return $this->channelsdir . DIRECTORY_SEPARATOR . '.alias' . + DIRECTORY_SEPARATOR . str_replace('/', '_', strtolower($alias)) . '.txt'; + } + + /** + * Get the name of a channel from its alias + */ + function _getChannelFromAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear.php.net'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl.php.net'; + } + + if ($channel == 'doc.php.net') { + return 'doc.php.net'; + } + + if ($channel == '__uri') { + return '__uri'; + } + + return false; + } + + $channel = strtolower($channel); + if (file_exists($this->_getChannelAliasFileName($channel))) { + // translate an alias to an actual channel + return implode('', file($this->_getChannelAliasFileName($channel))); + } + + return $channel; + } + + /** + * Get the alias of a channel from its alias or its name + */ + function _getAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl'; + } + + if ($channel == 'doc.php.net') { + return 'phpdocs'; + } + + return false; + } + + $channel = $this->_getChannel($channel); + if (PEAR::isError($channel)) { + return $channel; + } + + return $channel->getAlias(); + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _channelDirectoryName($channel) + { + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return $this->statedir; + } + + $ch = $this->_getChannelFromAlias($channel); + if (!$ch) { + $ch = $channel; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower('.channel.' . + str_replace('/', '_', $ch)); + } + + function _openPackageFile($package, $mode, $channel = false) + { + if (!$this->_assertStateDir($channel)) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_packageFileName($package, $channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closePackageFile($fp) + { + fclose($fp); + } + + function _openChannelFile($channel, $mode) + { + if (!$this->_assertChannelDir()) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_channelFileName($channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closeChannelFile($fp) + { + fclose($fp); + } + + function _rebuildFileMap() + { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } + + $channels = $this->_listAllPackages(); + $files = array(); + foreach ($channels as $channel => $packages) { + foreach ($packages as $package) { + $version = $this->_packageInfo($package, 'version', $channel); + $filelist = $this->_packageInfo($package, 'filelist', $channel); + if (!is_array($filelist)) { + continue; + } + + foreach ($filelist as $name => $attrs) { + if (isset($attrs['attribs'])) { + $attrs = $attrs['attribs']; + } + + // it is possible for conflicting packages in different channels to + // conflict with data files/doc files + if ($name == 'dirtree') { + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = $package; + } + + if (isset($attrs['baseinstalldir'])) { + $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name; + } else { + $file = $name; + } + + $file = preg_replace(',^/+,', '', $file); + if ($channel != 'pear.php.net') { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = array(strtolower($channel), + strtolower($package)); + } else { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = strtolower($package); + } + } + } + } + + + $this->_assertStateDir(); + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->filemap, 'wb'); + if (!$fp) { + return false; + } + + $this->filemap_cache = $files; + fwrite($fp, serialize($files)); + fclose($fp); + return true; + } + + function _readFileMap() + { + if (!file_exists($this->filemap)) { + return array(); + } + + $fp = @fopen($this->filemap, 'r'); + if (!$fp) { + return $this->raiseError('PEAR_Registry: could not open filemap "' . $this->filemap . '"', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg); + } + + clearstatcache(); + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + $fsize = filesize($this->filemap); + fclose($fp); + $data = file_get_contents($this->filemap); + set_magic_quotes_runtime($rt); + $tmp = unserialize($data); + if (!$tmp && $fsize > 7) { + return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data); + } + + $this->filemap_cache = $tmp; + return true; + } + + /** + * Lock the registry. + * + * @param integer lock mode, one of LOCK_EX, LOCK_SH or LOCK_UN. + * See flock manual for more information. + * + * @return bool TRUE on success, FALSE if locking failed, or a + * PEAR error if some other error occurs (such as the + * lock file not being writable). + * + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->lock_fp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + if (!$this->_assertStateDir()) { + if ($mode == LOCK_EX) { + return $this->raiseError('Registry directory is not writeable by the current user'); + } + + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH || $mode === LOCK_UN) { + if (!file_exists($this->lockfile)) { + touch($this->lockfile); + } + $open_mode = 'r'; + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = @fopen($this->lockfile, $open_mode); + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = null; + return $this->raiseError("could not create lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->lock_fp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + //is resource at this point, close it on error. + fclose($this->lock_fp); + $this->lock_fp = null; + return $this->raiseError("could not acquire $str lock ($this->lockfile)", + PEAR_REGISTRY_ERROR_LOCK); + } + + return true; + } + + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->lock_fp)) { + fclose($this->lock_fp); + } + + $this->lock_fp = null; + return $ret; + } + + function _packageExists($package, $channel = false) + { + return file_exists($this->_packageFileName($package, $channel)); + } + + /** + * Determine whether a channel exists in the registry + * + * @param string Channel name + * @param bool if true, then aliases will be ignored + * @return boolean + */ + function _channelExists($channel, $noaliases = false) + { + $a = file_exists($this->_channelFileName($channel, $noaliases)); + if (!$a && $channel == 'pear.php.net') { + return true; + } + + if (!$a && $channel == 'pecl.php.net') { + return true; + } + + if (!$a && $channel == 'doc.php.net') { + return true; + } + + return $a; + } + + /** + * Determine whether a mirror exists within the deafult channel in the registry + * + * @param string Channel name + * @param string Mirror name + * + * @return boolean + */ + function _mirrorExists($channel, $mirror) + { + $data = $this->_channelInfo($channel); + if (!isset($data['servers']['mirror'])) { + return false; + } + + foreach ($data['servers']['mirror'] as $m) { + if ($m['attribs']['host'] == $mirror) { + return true; + } + } + + return false; + } + + /** + * @param PEAR_ChannelFile Channel object + * @param donotuse + * @param string Last-Modified HTTP tag from remote request + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function _addChannel($channel, $update = false, $lastmodified = false) + { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + + if (file_exists($this->_channelFileName($channel->getName()))) { + if (!$update) { + return false; + } + + $checker = $this->_getChannel($channel->getName()); + if (PEAR::isError($checker)) { + return $checker; + } + + if ($channel->getAlias() != $checker->getAlias()) { + if (file_exists($this->_getChannelAliasFileName($checker->getAlias()))) { + @unlink($this->_getChannelAliasFileName($checker->getAlias())); + } + } + } else { + if ($update && !in_array($channel->getName(), array('pear.php.net', 'pecl.php.net', 'doc.php.net'))) { + return false; + } + } + + $ret = $this->_assertChannelDir(); + if (PEAR::isError($ret)) { + return $ret; + } + + $ret = $this->_assertChannelStateDir($channel->getName()); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($channel->getAlias() != $channel->getName()) { + if (file_exists($this->_getChannelAliasFileName($channel->getAlias())) && + $this->_getChannelFromAlias($channel->getAlias()) != $channel->getName()) { + $channel->setAlias($channel->getName()); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_getChannelAliasFileName($channel->getAlias()), 'w'); + if (!$fp) { + return false; + } + + fwrite($fp, $channel->getName()); + fclose($fp); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_channelFileName($channel->getName()), 'wb'); + if (!$fp) { + return false; + } + + $info = $channel->toArray(); + if ($lastmodified) { + $info['_lastmodified'] = $lastmodified; + } else { + $info['_lastmodified'] = date('r'); + } + + fwrite($fp, serialize($info)); + fclose($fp); + return true; + } + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function _deleteChannel($channel) + { + if (!is_string($channel)) { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + $channel = $channel->getName(); + } + + if ($this->_getChannelFromAlias($channel) == '__uri') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + return false; + } + + if (!$this->_channelExists($channel)) { + return false; + } + + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return false; + } + + $channel = $this->_getChannelFromAlias($channel); + if ($channel == 'pear.php.net') { + return false; + } + + $test = $this->_listChannelPackages($channel); + if (count($test)) { + return false; + } + + $test = @rmdir($this->_channelDirectoryName($channel)); + if (!$test) { + return false; + } + + $file = $this->_getChannelAliasFileName($this->_getAlias($channel)); + if (file_exists($file)) { + $test = @unlink($file); + if (!$test) { + return false; + } + } + + $file = $this->_channelFileName($channel); + $ret = true; + if (file_exists($file)) { + $ret = @unlink($file); + } + + return $ret; + } + + /** + * Determine whether a channel exists in the registry + * @param string Channel Alias + * @return boolean + */ + function _isChannelAlias($alias) + { + return file_exists($this->_getChannelAliasFileName($alias)); + } + + /** + * @param string|null + * @param string|null + * @param string|null + * @return array|null + * @access private + */ + function _packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if ($package === null) { + if ($channel === null) { + $channels = $this->_listChannels(); + $ret = array(); + foreach ($channels as $channel) { + $channel = strtolower($channel); + $ret[$channel] = array(); + $packages = $this->_listPackages($channel); + foreach ($packages as $package) { + $ret[$channel][] = $this->_packageInfo($package, null, $channel); + } + } + + return $ret; + } + + $ps = $this->_listPackages($channel); + if (!count($ps)) { + return array(); + } + return array_map(array(&$this, '_packageInfo'), + $ps, array_fill(0, count($ps), null), + array_fill(0, count($ps), $channel)); + } + + $fp = $this->_openPackageFile($package, 'r', $channel); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closePackageFile($fp); + $data = file_get_contents($this->_packageFileName($package, $channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + if ($key === null) { + return $data; + } + + // compatibility for package.xml version 2.0 + if (isset($data['old'][$key])) { + return $data['old'][$key]; + } + + if (isset($data[$key])) { + return $data[$key]; + } + + return null; + } + + /** + * @param string Channel name + * @param bool whether to strictly retrieve info of channels, not just aliases + * @return array|null + */ + function _channelInfo($channel, $noaliases = false) + { + if (!$this->_channelExists($channel, $noaliases)) { + return null; + } + + $fp = $this->_openChannelFile($channel, 'r'); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closeChannelFile($fp); + $data = file_get_contents($this->_channelFileName($channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + return $data; + } + + function _listChannels() + { + $channellist = array(); + if (!file_exists($this->channelsdir) || !is_dir($this->channelsdir)) { + return array('pear.php.net', 'pecl.php.net', 'doc.php.net', '__uri'); + } + + $dp = opendir($this->channelsdir); + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + if ($ent == '__uri.reg') { + $channellist[] = '__uri'; + continue; + } + + $channellist[] = str_replace('_', '/', substr($ent, 0, -4)); + } + + closedir($dp); + if (!in_array('pear.php.net', $channellist)) { + $channellist[] = 'pear.php.net'; + } + + if (!in_array('pecl.php.net', $channellist)) { + $channellist[] = 'pecl.php.net'; + } + + if (!in_array('doc.php.net', $channellist)) { + $channellist[] = 'doc.php.net'; + } + + + if (!in_array('__uri', $channellist)) { + $channellist[] = '__uri'; + } + + natsort($channellist); + return $channellist; + } + + function _listPackages($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_listChannelPackages($channel); + } + + if (!file_exists($this->statedir) || !is_dir($this->statedir)) { + return array(); + } + + $pkglist = array(); + $dp = opendir($this->statedir); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + $pkglist[] = substr($ent, 0, -4); + } + closedir($dp); + return $pkglist; + } + + function _listChannelPackages($channel) + { + $pkglist = array(); + if (!file_exists($this->_channelDirectoryName($channel)) || + !is_dir($this->_channelDirectoryName($channel))) { + return array(); + } + + $dp = opendir($this->_channelDirectoryName($channel)); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + $pkglist[] = substr($ent, 0, -4); + } + + closedir($dp); + return $pkglist; + } + + function _listAllPackages() + { + $ret = array(); + foreach ($this->_listChannels() as $channel) { + $ret[$channel] = $this->_listPackages($channel); + } + + return $ret; + } + + /** + * Add an installed package to the registry + * @param string package name + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + * @access private + */ + function _addPackage($package, $info) + { + if ($this->_packageExists($package)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb'); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($info['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _addPackage2($info) + { + if (!is_a($info, 'PEAR_PackageFile_v1') && !is_a($info, 'PEAR_PackageFile_v2')) { + return false; + } + + if (!$info->validate()) { + if (class_exists('PEAR_Common')) { + $ui = PEAR_Frontend::singleton(); + if ($ui) { + foreach ($info->getValidationWarnings() as $err) { + $ui->log($err['message'], true); + } + } + } + return false; + } + + $channel = $info->getChannel(); + $package = $info->getPackage(); + $save = $info; + if ($this->_packageExists($package, $channel)) { + return false; + } + + if (!$this->_channelExists($channel, true)) { + return false; + } + + $info = $info->toArray(true); + if (!$info) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb', $channel); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param array parsed package.xml 1.0 + * @param bool this parameter is only here for BC. Don't use it. + * @access private + */ + function _updatePackage($package, $info, $merge = true) + { + $oldinfo = $this->_packageInfo($package); + if (empty($oldinfo)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'w'); + if ($fp === null) { + return false; + } + + if (is_object($info)) { + $info = $info->toArray(); + } + $info['_lastmodified'] = time(); + + $newinfo = $info; + if ($merge) { + $info = array_merge($oldinfo, $info); + } else { + $diff = $info; + } + + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($newinfo['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _updatePackage2($info) + { + if (!$this->_packageExists($info->getPackage(), $info->getChannel())) { + return false; + } + + $fp = $this->_openPackageFile($info->getPackage(), 'w', $info->getChannel()); + if ($fp === null) { + return false; + } + + $save = $info; + $info = $save->getArray(true); + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param string Channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + * @access private + */ + function &_getPackage($package, $channel = 'pear.php.net') + { + $info = $this->_packageInfo($package, null, $channel); + if ($info === null) { + return $info; + } + + $a = $this->_config; + if (!$a) { + $this->_config = &new PEAR_Config; + $this->_config->set('php_dir', $this->statedir); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->_config); + $pf = &$pkg->fromArray($info); + return $pf; + } + + /** + * @param string channel name + * @param bool whether to strictly retrieve channel names + * @return PEAR_ChannelFile|PEAR_Error + * @access private + */ + function &_getChannel($channel, $noaliases = false) + { + $ch = false; + if ($this->_channelExists($channel, $noaliases)) { + $chinfo = $this->_channelInfo($channel, $noaliases); + if ($chinfo) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $ch = &PEAR_ChannelFile::fromArrayWithErrors($chinfo); + } + } + + if ($ch) { + if ($ch->validate()) { + return $ch; + } + + foreach ($ch->getErrors(true) as $err) { + $message = $err['message'] . "\n"; + } + + $ch = PEAR::raiseError($message); + return $ch; + } + + if ($this->_getChannelFromAlias($channel) == 'pear.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pecl.php.net'); + $pear_channel->setAlias('pecl'); + $pear_channel->setSummary('PHP Extension Community Library'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pear_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('phpdocs'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + return $doc_channel; + } + + + if ($this->_getChannelFromAlias($channel) == '__uri') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + return $private; + } + + return $ch; + } + + /** + * @param string Package name + * @param string Channel name + * @return bool + */ + function packageExists($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageExists($package, $channel); + $this->_unlock(); + return $ret; + } + + /** + * @param string channel name + * @param bool if true, then aliases will be ignored + * @return bool + */ + function channelExists($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelExists($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + /** + * @param string channel name mirror is in + * @param string mirror name + * + * @return bool + */ + function mirrorExists($channel, $mirror) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + + $ret = $this->_mirrorExists($channel, $mirror); + $this->_unlock(); + return $ret; + } + + /** + * Determines whether the parameter is an alias of a channel + * @param string + * @return bool + */ + function isAlias($alias) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_isChannelAlias($alias); + $this->_unlock(); + return $ret; + } + + /** + * @param string|null + * @param string|null + * @param string + * @return array|null + */ + function packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageInfo($package, $key, $channel); + $this->_unlock(); + return $ret; + } + + /** + * Retrieve a raw array of channel data. + * + * Do not use this, instead use {@link getChannel()} for normal + * operations. Array structure is undefined in this method + * @param string channel name + * @param bool whether to strictly retrieve information only on non-aliases + * @return array|null|PEAR_Error + */ + function channelInfo($channel = null, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelInfo($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + /** + * @param string + */ + function channelName($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getChannelFromAlias($channel); + $this->_unlock(); + return $ret; + } + + /** + * @param string + */ + function channelAlias($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getAlias($channel); + $this->_unlock(); + return $ret; + } + + function listPackages($channel = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listPackages($channel); + $this->_unlock(); + return $ret; + } + + function listAllPackages() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listAllPackages(); + $this->_unlock(); + return $ret; + } + + function listChannels() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listChannels(); + $this->_unlock(); + return $ret; + } + + /** + * Add an installed package to the registry + * @param string|PEAR_PackageFile_v1|PEAR_PackageFile_v2 package name or object + * that will be passed to {@link addPackage2()} + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + */ + function addPackage($package, $info) + { + if (is_object($info)) { + return $this->addPackage2($info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage($package, $info); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($info); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + function addPackage2($info) + { + if (!is_object($info)) { + return $this->addPackage($info['package'], $info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + return $ret; + } + + /** + * For future expandibility purposes, separate this + * @param PEAR_ChannelFile + */ + function updateChannel($channel, $lastmodified = null) + { + if ($channel->getName() == '__uri') { + return false; + } + return $this->addChannel($channel, $lastmodified, true); + } + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function deleteChannel($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_deleteChannel($channel); + $this->_unlock(); + if ($ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + /** + * @param PEAR_ChannelFile Channel object + * @param string Last-Modified header from HTTP for caching + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function addChannel($channel, $lastmodified = false, $update = false) + { + if (!is_a($channel, 'PEAR_ChannelFile') || !$channel->validate()) { + if (!$channel->validate()) { + $msg = ''; + $errors = $channel->getErrors(); + foreach ($errors as $error) { + $msg .= $error['message'] . ', '; + } + return PEAR::raiseError(substr($msg, 0, -2)); + } + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_addChannel($channel, $update, $lastmodified); + $this->_unlock(); + if (!$update && $ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + function deletePackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $file = $this->_packageFileName($package, $channel); + $ret = file_exists($file) ? @unlink($file) : false; + $this->_rebuildFileMap(); + $this->_unlock(); + $p = array('channel' => $channel, 'package' => $package); + $this->_dependencyDB->uninstallPackage($p); + return $ret; + } + + function updatePackage($package, $info, $merge = true) + { + if (is_object($info)) { + return $this->updatePackage2($info, $merge); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_updatePackage($package, $info, $merge); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($this->packageInfo($package)); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + function updatePackage2($info) + { + + if (!is_object($info)) { + return $this->updatePackage($info['package'], $info, $merge); + } + + if (!$info->validate(PEAR_VALIDATE_DOWNLOADING)) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_updatePackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + + return $ret; + } + + /** + * @param string channel name + * @param bool whether to strictly return raw channels (no aliases) + * @return PEAR_ChannelFile|PEAR_Error + */ + function &getChannel($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = &$this->_getChannel($channel, $noaliases); + $this->_unlock(); + if (!$ret) { + return PEAR::raiseError('Unknown channel: ' . $channel); + } + return $ret; + } + + /** + * @param string package name + * @param string channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + */ + function &getPackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $pf = &$this->_getPackage($package, $channel); + $this->_unlock(); + return $pf; + } + + /** + * Get PEAR_PackageFile_v[1/2] objects representing the contents of + * a dependency group that are installed. + * + * This is used at uninstall-time + * @param array + * @return array|false + */ + function getInstalledGroup($group) + { + $ret = array(); + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + foreach ($group['package'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + foreach ($group['subpackage'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (!count($ret)) { + return false; + } + return $ret; + } + + /** + * @param string channel name + * @return PEAR_Validate|false + */ + function &getChannelValidator($channel) + { + $chan = $this->getChannel($channel); + if (PEAR::isError($chan)) { + return $chan; + } + $val = $chan->getValidationObject(); + return $val; + } + + /** + * @param string channel name + * @return array an array of PEAR_ChannelFile objects representing every installed channel + */ + function &getChannels() + { + $ret = array(); + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + foreach ($this->_listChannels() as $channel) { + $e = &$this->_getChannel($channel); + if (!$e || PEAR::isError($e)) { + continue; + } + $ret[] = $e; + } + $this->_unlock(); + return $ret; + } + + /** + * Test whether a file or set of files belongs to a package. + * + * If an array is passed in + * @param string|array file path, absolute or relative to the pear + * install dir + * @param string|array name of PEAR package or array('package' => name, 'channel' => + * channel) of a package that will be ignored + * @param string API version - 1.1 will exclude any files belonging to a package + * @param array private recursion variable + * @return array|false which package and channel the file belongs to, or an empty + * string if the file does not belong to an installed package, + * or belongs to the second parameter's package + */ + function checkFileMap($path, $package = false, $api = '1.0', $attrs = false) + { + if (is_array($path)) { + static $notempty; + if (empty($notempty)) { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } + $notempty = create_function('$a','return !empty($a);'); + } + $package = is_array($package) ? array(strtolower($package[0]), strtolower($package[1])) + : strtolower($package); + $pkgs = array(); + foreach ($path as $name => $attrs) { + if (is_array($attrs)) { + if (isset($attrs['install-as'])) { + $name = $attrs['install-as']; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = is_array($package) ? $package[1] : $package; + } + if (isset($attrs['baseinstalldir'])) { + $name = $attrs['baseinstalldir'] . DIRECTORY_SEPARATOR . $name; + } + } + $pkgs[$name] = $this->checkFileMap($name, $package, $api, $attrs); + if (PEAR::isError($pkgs[$name])) { + return $pkgs[$name]; + } + } + return array_filter($pkgs, $notempty); + } + if (empty($this->filemap_cache)) { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $err = $this->_readFileMap(); + $this->_unlock(); + if (PEAR::isError($err)) { + return $err; + } + } + if (!$attrs) { + $attrs = array('role' => 'php'); // any old call would be for PHP role only + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + $l = strlen($this->install_dir); + if (substr($path, 0, $l) == $this->install_dir) { + $path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l)); + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + return false; + } + + /** + * Force a reload of the filemap + * @since 1.5.0RC3 + */ + function flushFileMap() + { + $this->filemap_cache = null; + clearstatcache(); // ensure that the next read gets the full, current filemap + } + + /** + * Get the expected API version. Channels API is version 1.1, as it is backwards + * compatible with 1.0 + * @return string + */ + function apiVersion() + { + return '1.1'; + } + + /** + * Parse a package name, or validate a parsed package name array + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',] + * ['group' => 'groupname']) + * or a string of format + * [channel://][channame/]pname[-version|-state][/group=groupname] + * @return array|PEAR_Error + */ + function parsePackageName($param, $defaultchannel = 'pear.php.net') + { + $saveparam = $param; + if (is_array($param)) { + // convert to string for error messages + $saveparam = $this->parsedPackageNameToString($param); + // process the array + if (!isset($param['package'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in index "param"', + 'package', null, null, $param); + } + if (!isset($param['uri'])) { + if (!isset($param['channel'])) { + $param['channel'] = $defaultchannel; + } + } else { + $param['channel'] = '__uri'; + } + } else { + $components = @parse_url((string) $param); + if (isset($components['scheme'])) { + if ($components['scheme'] == 'http') { + // uri package + $param = array('uri' => $param, 'channel' => '__uri'); + } elseif($components['scheme'] != 'channel') { + return PEAR::raiseError('parsePackageName(): only channel:// uris may ' . + 'be downloaded, not "' . $param . '"', 'invalid', null, null, $param); + } + } + if (!isset($components['path'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in "' . $param . '"', + 'package', null, null, $param); + } + if (isset($components['host'])) { + // remove the leading "/" + $components['path'] = substr($components['path'], 1); + } + if (!isset($components['scheme'])) { + if (strpos($components['path'], '/') !== false) { + if ($components['path']{0} == '/') { + return PEAR::raiseError('parsePackageName(): this is not ' . + 'a package name, it begins with "/" in "' . $param . '"', + 'invalid', null, null, $param); + } + $parts = explode('/', $components['path']); + $components['host'] = array_shift($parts); + if (count($parts) > 1) { + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } else { + $components['path'] = implode('/', $parts); + } + } else { + $components['host'] = $defaultchannel; + } + } else { + if (strpos($components['path'], '/')) { + $parts = explode('/', $components['path']); + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } + } + + if (is_array($param)) { + $param['package'] = $components['path']; + } else { + $param = array( + 'package' => $components['path'] + ); + if (isset($components['host'])) { + $param['channel'] = $components['host']; + } + } + if (isset($components['fragment'])) { + $param['group'] = $components['fragment']; + } + if (isset($components['user'])) { + $param['user'] = $components['user']; + } + if (isset($components['pass'])) { + $param['pass'] = $components['pass']; + } + if (isset($components['query'])) { + parse_str($components['query'], $param['opts']); + } + // check for extension + $pathinfo = pathinfo($param['package']); + if (isset($pathinfo['extension']) && + in_array(strtolower($pathinfo['extension']), array('tgz', 'tar'))) { + $param['extension'] = $pathinfo['extension']; + $param['package'] = substr($pathinfo['basename'], 0, + strlen($pathinfo['basename']) - 4); + } + // check for version + if (strpos($param['package'], '-')) { + $test = explode('-', $param['package']); + if (count($test) != 2) { + return PEAR::raiseError('parsePackageName(): only one version/state ' . + 'delimiter "-" is allowed in "' . $saveparam . '"', + 'version', null, null, $param); + } + list($param['package'], $param['version']) = $test; + } + } + // validation + $info = $this->channelExists($param['channel']); + if (PEAR::isError($info)) { + return $info; + } + if (!$info) { + return PEAR::raiseError('unknown channel "' . $param['channel'] . + '" in "' . $saveparam . '"', 'channel', null, null, $param); + } + $chan = $this->getChannel($param['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + if (!$chan) { + return PEAR::raiseError("Exception: corrupt registry, could not " . + "retrieve channel " . $param['channel'] . " information", + 'registry', null, null, $param); + } + $param['channel'] = $chan->getName(); + $validate = $chan->getValidationObject(); + $vpackage = $chan->getValidationPackage(); + // validate package name + if (!$validate->validPackageName($param['package'], $vpackage['_content'])) { + return PEAR::raiseError('parsePackageName(): invalid package name "' . + $param['package'] . '" in "' . $saveparam . '"', + 'package', null, null, $param); + } + if (isset($param['group'])) { + if (!PEAR_Validate::validGroupName($param['group'])) { + return PEAR::raiseError('parsePackageName(): dependency group "' . $param['group'] . + '" is not a valid group name in "' . $saveparam . '"', 'group', null, null, + $param); + } + } + if (isset($param['state'])) { + if (!in_array(strtolower($param['state']), $validate->getValidStates())) { + return PEAR::raiseError('parsePackageName(): state "' . $param['state'] + . '" is not a valid state in "' . $saveparam . '"', + 'state', null, null, $param); + } + } + if (isset($param['version'])) { + if (isset($param['state'])) { + return PEAR::raiseError('parsePackageName(): cannot contain both ' . + 'a version and a stability (state) in "' . $saveparam . '"', + 'version/state', null, null, $param); + } + // check whether version is actually a state + if (in_array(strtolower($param['version']), $validate->getValidStates())) { + $param['state'] = strtolower($param['version']); + unset($param['version']); + } else { + if (!$validate->validVersion($param['version'])) { + return PEAR::raiseError('parsePackageName(): "' . $param['version'] . + '" is neither a valid version nor a valid state in "' . + $saveparam . '"', 'version/state', null, null, $param); + } + } + } + return $param; + } + + /** + * @param array + * @return string + */ + function parsedPackageNameToString($parsed, $brief = false) + { + if (is_string($parsed)) { + return $parsed; + } + if (is_object($parsed)) { + $p = $parsed; + $parsed = array( + 'package' => $p->getPackage(), + 'channel' => $p->getChannel(), + 'version' => $p->getVersion(), + ); + } + if (isset($parsed['uri'])) { + return $parsed['uri']; + } + if ($brief) { + if ($channel = $this->channelAlias($parsed['channel'])) { + return $channel . '/' . $parsed['package']; + } + } + $upass = ''; + if (isset($parsed['user'])) { + $upass = $parsed['user']; + if (isset($parsed['pass'])) { + $upass .= ':' . $parsed['pass']; + } + $upass = "$upass@"; + } + $ret = 'channel://' . $upass . $parsed['channel'] . '/' . $parsed['package']; + if (isset($parsed['version']) || isset($parsed['state'])) { + $ver = isset($parsed['version']) ? $parsed['version'] : ''; + $ver .= isset($parsed['state']) ? $parsed['state'] : ''; + $ret .= '-' . $ver; + } + if (isset($parsed['extension'])) { + $ret .= '.' . $parsed['extension']; + } + if (isset($parsed['opts'])) { + $ret .= '?'; + foreach ($parsed['opts'] as $name => $value) { + $parsed['opts'][$name] = "$name=$value"; + } + $ret .= implode('&', $parsed['opts']); + } + if (isset($parsed['group'])) { + $ret .= '#' . $parsed['group']; + } + return $ret; + } +} diff --git a/includes/pear/PEAR/RunTest.php b/includes/pear/PEAR/RunTest.php new file mode 100644 index 0000000..a916af4 --- /dev/null +++ b/includes/pear/PEAR/RunTest.php @@ -0,0 +1,973 @@ +<?php +/** + * PEAR_RunTest + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.3 + */ + +/** + * for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Config.php'; + +define('DETAILED', 1); +putenv("PHP_PEAR_RUNTESTS=1"); + +/** + * Simplified version of PHP's test suite + * + * Try it with: + * + * $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);' + * + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.3 + */ +class PEAR_RunTest +{ + var $_headers = array(); + var $_logger; + var $_options; + var $_php; + var $tests_count; + var $xdebug_loaded; + /** + * Saved value of php executable, used to reset $_php when we + * have a test that uses cgi + * + * @var unknown_type + */ + var $_savephp; + var $ini_overwrites = array( + 'output_handler=', + 'open_basedir=', + 'safe_mode=0', + 'disable_functions=', + 'output_buffering=Off', + 'display_errors=1', + 'log_errors=0', + 'html_errors=0', + 'track_errors=1', + 'report_memleaks=0', + 'report_zend_debug=0', + 'docref_root=', + 'docref_ext=.html', + 'error_prepend_string=', + 'error_append_string=', + 'auto_prepend_file=', + 'auto_append_file=', + 'magic_quotes_runtime=0', + 'xdebug.default_enable=0', + 'allow_url_fopen=1', + ); + + /** + * An object that supports the PEAR_Common->log() signature, or null + * @param PEAR_Common|null + */ + function PEAR_RunTest($logger = null, $options = array()) + { + if (!defined('E_DEPRECATED')) { + define('E_DEPRECATED', 0); + } + if (!defined('E_STRICT')) { + define('E_STRICT', 0); + } + $this->ini_overwrites[] = 'error_reporting=' . (E_ALL & ~(E_DEPRECATED | E_STRICT)); + if (is_null($logger)) { + require_once 'PEAR/Common.php'; + $logger = new PEAR_Common; + } + $this->_logger = $logger; + $this->_options = $options; + + $conf = &PEAR_Config::singleton(); + $this->_php = $conf->get('php_bin'); + } + + /** + * Taken from php-src/run-tests.php + * + * @param string $commandline command name + * @param array $env + * @param string $stdin standard input to pass to the command + * @return unknown + */ + function system_with_timeout($commandline, $env = null, $stdin = null) + { + $data = ''; + if (version_compare(phpversion(), '5.0.0', '<')) { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes); + } else { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes, null, $env, array('suppress_errors' => true)); + } + + if (!$proc) { + return false; + } + + if (is_string($stdin)) { + fwrite($pipes[0], $stdin); + } + fclose($pipes[0]); + + while (true) { + /* hide errors from interrupted syscalls */ + $r = $pipes; + $e = $w = null; + $n = @stream_select($r, $w, $e, 60); + + if ($n === 0) { + /* timed out */ + $data .= "\n ** ERROR: process timed out **\n"; + proc_terminate($proc); + return array(1234567890, $data); + } else if ($n > 0) { + $line = fread($pipes[1], 8192); + if (strlen($line) == 0) { + /* EOF */ + break; + } + $data .= $line; + } + } + if (function_exists('proc_get_status')) { + $stat = proc_get_status($proc); + if ($stat['signaled']) { + $data .= "\nTermsig=".$stat['stopsig']; + } + } + $code = proc_close($proc); + if (function_exists('proc_get_status')) { + $code = $stat['exitcode']; + } + return array($code, $data); + } + + /** + * Turns a PHP INI string into an array + * + * Turns -d "include_path=/foo/bar" into this: + * array( + * 'include_path' => array( + * 'operator' => '-d', + * 'value' => '/foo/bar', + * ) + * ) + * Works both with quotes and without + * + * @param string an PHP INI string, -d "include_path=/foo/bar" + * @return array + */ + function iniString2array($ini_string) + { + if (!$ini_string) { + return array(); + } + $split = preg_split('/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY); + $key = $split[1][0] == '"' ? substr($split[1], 1) : $split[1]; + $value = $split[2][strlen($split[2]) - 1] == '"' ? substr($split[2], 0, -1) : $split[2]; + // FIXME review if this is really the struct to go with + $array = array($key => array('operator' => $split[0], 'value' => $value)); + return $array; + } + + function settings2array($settings, $ini_settings) + { + foreach ($settings as $setting) { + if (strpos($setting, '=') !== false) { + $setting = explode('=', $setting, 2); + $name = trim(strtolower($setting[0])); + $value = trim($setting[1]); + $ini_settings[$name] = $value; + } + } + return $ini_settings; + } + + function settings2params($ini_settings) + { + $settings = ''; + foreach ($ini_settings as $name => $value) { + if (is_array($value)) { + $operator = $value['operator']; + $value = $value['value']; + } else { + $operator = '-d'; + } + $value = addslashes($value); + $settings .= " $operator \"$name=$value\""; + } + return $settings; + } + + function _preparePhpBin($php, $file, $ini_settings) + { + $file = escapeshellarg($file); + // This was fixed in php 5.3 and is not needed after that + if (OS_WINDOWS && version_compare(PHP_VERSION, '5.3', '<')) { + $cmd = '"'.escapeshellarg($php).' '.$ini_settings.' -f ' . $file .'"'; + } else { + $cmd = $php . $ini_settings . ' -f ' . $file; + } + + return $cmd; + } + + function runPHPUnit($file, $ini_settings = '') + { + if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) { + $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file); + } elseif (file_exists($file)) { + $file = realpath($file); + } + + $cmd = $this->_preparePhpBin($this->_php, $file, $ini_settings); + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + $savedir = getcwd(); // in case the test moves us around + chdir(dirname($file)); + echo `$cmd`; + chdir($savedir); + return 'PASSED'; // we have no way of knowing this information so assume passing + } + + /** + * Runs an individual test case. + * + * @param string The filename of the test + * @param array|string INI settings to be applied to the test run + * @param integer Number what the current running test is of the + * whole test suite being runned. + * + * @return string|object Returns PASSED, WARNED, FAILED depending on how the + * test came out. + * PEAR Error when the tester it self fails + */ + function run($file, $ini_settings_cmd_line = array(), $test_number = 1) + { + if (isset($this->_savephp)) { + $this->_php = $this->_savephp; + unset($this->_savephp); + } + if (empty($this->_options['cgi'])) { + // try to see if php-cgi is in the path + $res = $this->system_with_timeout('php-cgi -v'); + if (false !== $res && !(is_array($res) && in_array($res[0], array(-1, 127)))) { + $this->_options['cgi'] = 'php-cgi'; + } + } + if (1 < $len = strlen($this->tests_count)) { + $test_number = str_pad($test_number, $len, ' ', STR_PAD_LEFT); + $test_nr = "[$test_number/$this->tests_count] "; + } else { + $test_nr = ''; + } + + $file = realpath($file); + $section_text = $this->_readFile($file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (isset($section_text['POST_RAW']) && isset($section_text['UPLOAD'])) { + return PEAR::raiseError("Cannot contain both POST_RAW and UPLOAD in test file: $file"); + } + + $cwd = getcwd(); + + $pass_options = ''; + if (!empty($this->_options['ini'])) { + $pass_options = $this->_options['ini']; + } + + if (is_string($ini_settings_cmd_line)) { + $ini_settings_cmd_line = $this->iniString2array($ini_settings_cmd_line); + } + + $ini_settings_base = $this->settings2array($this->ini_overwrites, $ini_settings_cmd_line); + if ($section_text['INI']) { + if (strpos($section_text['INI'], '{PWD}') !== false) { + $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); + } + $ini = preg_split( "/[\n\r]+/", $section_text['INI']); + $ini_settings_phpt_section = $this->settings2array($ini, array()); + $ini_settings_phpt_section = $this->settings2params($ini_settings_phpt_section); + } else { + $ini_settings_phpt_section = ''; + } + $ini_settings_base = $this->settings2params($ini_settings_base); + $ini_settings_all = $ini_settings_base . ' ' . $ini_settings_phpt_section; + + $shortname = str_replace($cwd . DIRECTORY_SEPARATOR, '', $file); + + $tested = trim($section_text['TEST']); + $tested.= !isset($this->_options['simple']) ? "[$shortname]" : ' '; + + if (!empty($section_text['POST']) || !empty($section_text['POST_RAW']) || + !empty($section_text['UPLOAD']) || !empty($section_text['GET']) || + !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { + if (empty($this->_options['cgi'])) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip --cgi option needed for this test, "pear help run-tests" for info'); + } + return 'SKIPPED'; + } + $this->_savephp = $this->_php; + $this->_php = $this->_options['cgi']; + } + + $temp_dir = realpath(dirname($file)); + $main_file_name = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + $this->_cleanupOldFiles($file); + + // Check if test should be skipped. + $res = $this->_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings_base); + if (count($res) != 2) { + return $res; + } + $info = $res['info']; + $warn = $res['warn']; + + // We've satisfied the preconditions - run the test! + if (isset($this->_options['coverage']) && $this->xdebug_loaded) { + $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'xdebug'; + $text = "\n" . 'function coverage_shutdown() {' . + "\n" . ' $xdebug = var_export(xdebug_get_code_coverage(), true);'; + if (!function_exists('file_put_contents')) { + $text .= "\n" . ' $fh = fopen(\'' . $xdebug_file . '\', "wb");' . + "\n" . ' if ($fh !== false) {' . + "\n" . ' fwrite($fh, $xdebug);' . + "\n" . ' fclose($fh);' . + "\n" . ' }'; + } else { + $text .= "\n" . ' file_put_contents(\'' . $xdebug_file . '\', $xdebug);'; + } + + // Workaround for http://pear.php.net/bugs/bug.php?id=17292 + $lines = explode("\n", $section_text['FILE']); + $numLines = count($lines); + $namespace = ''; + $coverage_shutdown = 'coverage_shutdown'; + + if ( + substr($lines[0], 0, 2) == '<?' || + substr($lines[0], 0, 5) == '<?php' + ) { + unset($lines[0]); + } + + + for ($i = 0; $i < $numLines; $i++) { + if (isset($lines[$i]) && substr($lines[$i], 0, 9) == 'namespace') { + $namespace = substr($lines[$i], 10, -1); + $coverage_shutdown = $namespace . '\\coverage_shutdown'; + $namespace = "namespace " . $namespace . ";\n"; + + unset($lines[$i]); + break; + } + } + + $text .= "\n xdebug_stop_code_coverage();" . + "\n" . '} // end coverage_shutdown()' . + "\n\n" . 'register_shutdown_function("' . $coverage_shutdown . '");'; + $text .= "\n" . 'xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);' . "\n"; + + $this->save_text($temp_file, "<?php\n" . $namespace . $text . "\n" . implode("\n", $lines)); + } else { + $this->save_text($temp_file, $section_text['FILE']); + } + + $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : ''; + $cmd = $this->_preparePhpBin($this->_php, $temp_file, $ini_settings_all); + $cmd.= "$args 2>&1"; + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + // Reset environment from any previous test. + $env = $this->_resetEnv($section_text, $temp_file); + + $section_text = $this->_processUpload($section_text, $file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) { + $post = trim($section_text['POST_RAW']); + $raw_lines = explode("\n", $post); + + $request = ''; + $started = false; + foreach ($raw_lines as $i => $line) { + if (empty($env['CONTENT_TYPE']) && + preg_match('/^Content-Type:(.*)/i', $line, $res)) { + $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); + continue; + } + if ($started) { + $request .= "\n"; + } + $started = true; + $request .= $line; + } + + $env['CONTENT_LENGTH'] = strlen($request); + $env['REQUEST_METHOD'] = 'POST'; + + $this->save_text($tmp_post, $request); + $cmd = "$this->_php $pass_options $ini_settings_all \"$temp_file\" 2>&1 < \"$tmp_post\""; + } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + $post = trim($section_text['POST']); + $this->save_text($tmp_post, $post); + $content_length = strlen($post); + + $env['REQUEST_METHOD'] = 'POST'; + $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + $env['CONTENT_LENGTH'] = $content_length; + + $cmd = "$this->_php $pass_options $ini_settings_all \"$temp_file\" 2>&1 < \"$tmp_post\""; + } else { + $env['REQUEST_METHOD'] = 'GET'; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + } + + if (OS_WINDOWS && isset($section_text['RETURNS'])) { + ob_start(); + system($cmd, $return_value); + $out = ob_get_contents(); + ob_end_clean(); + $section_text['RETURNS'] = (int) trim($section_text['RETURNS']); + $returnfail = ($return_value != $section_text['RETURNS']); + } else { + $returnfail = false; + $stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null; + $out = $this->system_with_timeout($cmd, $env, $stdin); + $return_value = $out[0]; + $out = $out[1]; + } + + $output = preg_replace('/\r\n/', "\n", trim($out)); + + if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) { + @unlink(realpath($tmp_post)); + } + chdir($cwd); // in case the test moves us around + + $this->_testCleanup($section_text, $temp_clean); + + /* when using CGI, strip the headers from the output */ + $output = $this->_stripHeadersCGI($output); + + if (isset($section_text['EXPECTHEADERS'])) { + $testheaders = $this->_processHeaders($section_text['EXPECTHEADERS']); + $missing = array_diff_assoc($testheaders, $this->_headers); + $changed = ''; + foreach ($missing as $header => $value) { + if (isset($this->_headers[$header])) { + $changed .= "-$header: $value\n+$header: "; + $changed .= $this->_headers[$header]; + } else { + $changed .= "-$header: $value\n"; + } + } + if ($missing) { + // tack on failed headers to output: + $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed"; + } + } + // Does the output match what is expected? + do { + if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { + if (isset($section_text['EXPECTF'])) { + $wanted = trim($section_text['EXPECTF']); + } else { + $wanted = trim($section_text['EXPECTREGEX']); + } + $wanted_re = preg_replace('/\r\n/', "\n", $wanted); + if (isset($section_text['EXPECTF'])) { + $wanted_re = preg_quote($wanted_re, '/'); + // Stick to basics + $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy + $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re); + $wanted_re = str_replace("%d", "[0-9]+", $wanted_re); + $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re); + $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re); + $wanted_re = str_replace("%c", ".", $wanted_re); + // %f allows two points "-.0.0" but that is the best *simple* expression + } + + /* DEBUG YOUR REGEX HERE + var_dump($wanted_re); + print(str_repeat('=', 80) . "\n"); + var_dump($output); + */ + if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } else { + if (isset($section_text['EXPECTFILE'])) { + $f = $temp_dir . '/' . trim($section_text['EXPECTFILE']); + if (!($fp = @fopen($f, 'rb'))) { + return PEAR::raiseError('--EXPECTFILE-- section file ' . + $f . ' not found'); + } + fclose($fp); + $section_text['EXPECT'] = file_get_contents($f); + } + + if (isset($section_text['EXPECT'])) { + $wanted = preg_replace('/\r\n/', "\n", trim($section_text['EXPECT'])); + } else { + $wanted = ''; + } + + // compare and leave on success + if (!$returnfail && 0 == strcmp($output, $wanted)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } + } while (false); + + if (array_key_exists('FAIL', $section_text)) { + // we expect a particular failure + // this is only used for testing PEAR_RunTest + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $faildiff = $this->generate_diff($wanted, $output, null, $expectf); + $faildiff = preg_replace('/\r/', '', $faildiff); + $wanted = preg_replace('/\r/', '', trim($section_text['FAIL'])); + if ($faildiff == $wanted) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + unset($section_text['EXPECTF']); + $output = $faildiff; + if (isset($section_text['RETURNS'])) { + return PEAR::raiseError('Cannot have both RETURNS and FAIL in the same test: ' . + $file); + } + } + + // Test failed so we need to report details. + $txt = $warn ? 'WARN ' : 'FAIL '; + $this->_logger->log(0, $txt . $test_nr . $tested . $info); + + // write .exp + $res = $this->_writeLog($exp_filename, $wanted); + if (PEAR::isError($res)) { + return $res; + } + + // write .out + $res = $this->_writeLog($output_filename, $output); + if (PEAR::isError($res)) { + return $res; + } + + // write .diff + $returns = isset($section_text['RETURNS']) ? + array(trim($section_text['RETURNS']), $return_value) : null; + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $data = $this->generate_diff($wanted, $output, $returns, $expectf); + $res = $this->_writeLog($diff_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + // write .log + $data = " +---- EXPECTED OUTPUT +$wanted +---- ACTUAL OUTPUT +$output +---- FAILED +"; + + if ($returnfail) { + $data .= " +---- EXPECTED RETURN +$section_text[RETURNS] +---- ACTUAL RETURN +$return_value +"; + } + + $res = $this->_writeLog($log_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + if (isset($this->_options['tapoutput'])) { + $wanted = explode("\n", $wanted); + $wanted = "# Expected output:\n#\n#" . implode("\n#", $wanted); + $output = explode("\n", $output); + $output = "#\n#\n# Actual output:\n#\n#" . implode("\n#", $output); + return array($wanted . $output . 'not ok', ' - ' . $tested); + } + return $warn ? 'WARNED' : 'FAILED'; + } + + function generate_diff($wanted, $output, $rvalue, $wanted_re) + { + $w = explode("\n", $wanted); + $o = explode("\n", $output); + $wr = explode("\n", $wanted_re); + $w1 = array_diff_assoc($w, $o); + $o1 = array_diff_assoc($o, $w); + $o2 = $w2 = array(); + foreach ($w1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || !isset($o1[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $o1[$idx])) { + $w2[sprintf("%03d<", $idx)] = sprintf("%03d- ", $idx + 1) . $val; + } + } + foreach ($o1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $val)) { + $o2[sprintf("%03d>", $idx)] = sprintf("%03d+ ", $idx + 1) . $val; + } + } + $diff = array_merge($w2, $o2); + ksort($diff); + $extra = $rvalue ? "##EXPECTED: $rvalue[0]\r\n##RETURNED: $rvalue[1]" : ''; + return implode("\r\n", $diff) . $extra; + } + + // Write the given text to a temporary file, and return the filename. + function save_text($filename, $text) + { + if (!$fp = fopen($filename, 'w')) { + return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)"); + } + fwrite($fp, $text); + fclose($fp); + if (1 < DETAILED) echo " +FILE $filename {{{ +$text +}}} +"; + } + + function _cleanupOldFiles($file) + { + $temp_dir = realpath(dirname($file)); + $mainFileName = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + @unlink($diff_filename); + @unlink($log_filename); + @unlink($exp_filename); + @unlink($output_filename); + @unlink($memcheck_filename); + @unlink($temp_file); + @unlink($temp_skipif); + @unlink($tmp_post); + @unlink($temp_clean); + } + + function _runSkipIf($section_text, $temp_skipif, $tested, $ini_settings) + { + $info = ''; + $warn = false; + if (array_key_exists('SKIPIF', $section_text) && trim($section_text['SKIPIF'])) { + $this->save_text($temp_skipif, $section_text['SKIPIF']); + $output = $this->system_with_timeout("$this->_php $ini_settings -f \"$temp_skipif\""); + $output = $output[1]; + $loutput = ltrim($output); + unlink($temp_skipif); + if (!strncasecmp('skip', $loutput, 4)) { + $skipreason = "SKIP $tested"; + if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) { + $skipreason .= '(reason: ' . $m[1] . ')'; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, $skipreason); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip ' . $reason); + } + return 'SKIPPED'; + } + + if (!strncasecmp('info', $loutput, 4) + && preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) { + $info = " (info: $m[1])"; + } + + if (!strncasecmp('warn', $loutput, 4) + && preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) { + $warn = true; /* only if there is a reason */ + $info = " (warn: $m[1])"; + } + } + + return array('warn' => $warn, 'info' => $info); + } + + function _stripHeadersCGI($output) + { + $this->headers = array(); + if (!empty($this->_options['cgi']) && + $this->_php == $this->_options['cgi'] && + preg_match("/^(.*?)(?:\n\n(.*)|\\z)/s", $output, $match)) { + $output = isset($match[2]) ? trim($match[2]) : ''; + $this->_headers = $this->_processHeaders($match[1]); + } + + return $output; + } + + /** + * Return an array that can be used with array_diff() to compare headers + * + * @param string $text + */ + function _processHeaders($text) + { + $headers = array(); + $rh = preg_split("/[\n\r]+/", $text); + foreach ($rh as $line) { + if (strpos($line, ':')!== false) { + $line = explode(':', $line, 2); + $headers[trim($line[0])] = trim($line[1]); + } + } + return $headers; + } + + function _readFile($file) + { + // Load the sections of the test file. + $section_text = array( + 'TEST' => '(unnamed test)', + 'SKIPIF' => '', + 'GET' => '', + 'COOKIE' => '', + 'POST' => '', + 'ARGS' => '', + 'INI' => '', + 'CLEAN' => '', + ); + + if (!is_file($file) || !$fp = fopen($file, "r")) { + return PEAR::raiseError("Cannot open test file: $file"); + } + + $section = ''; + while (!feof($fp)) { + $line = fgets($fp); + + // Match the beginning of a section. + if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { + $section = $r[1]; + $section_text[$section] = ''; + continue; + } elseif (empty($section)) { + fclose($fp); + return PEAR::raiseError("Invalid sections formats in test file: $file"); + } + + // Add to the section text. + $section_text[$section] .= $line; + } + fclose($fp); + + return $section_text; + } + + function _writeLog($logname, $data) + { + if (!$log = fopen($logname, 'w')) { + return PEAR::raiseError("Cannot create test log - $logname"); + } + fwrite($log, $data); + fclose($log); + } + + function _resetEnv($section_text, $temp_file) + { + $env = $_ENV; + $env['REDIRECT_STATUS'] = ''; + $env['QUERY_STRING'] = ''; + $env['PATH_TRANSLATED'] = ''; + $env['SCRIPT_FILENAME'] = ''; + $env['REQUEST_METHOD'] = ''; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + if (!empty($section_text['ENV'])) { + if (strpos($section_text['ENV'], '{PWD}') !== false) { + $section_text['ENV'] = str_replace('{PWD}', dirname($temp_file), $section_text['ENV']); + } + foreach (explode("\n", trim($section_text['ENV'])) as $e) { + $e = explode('=', trim($e), 2); + if (!empty($e[0]) && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } + } + if (array_key_exists('GET', $section_text)) { + $env['QUERY_STRING'] = trim($section_text['GET']); + } else { + $env['QUERY_STRING'] = ''; + } + if (array_key_exists('COOKIE', $section_text)) { + $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); + } else { + $env['HTTP_COOKIE'] = ''; + } + $env['REDIRECT_STATUS'] = '1'; + $env['PATH_TRANSLATED'] = $temp_file; + $env['SCRIPT_FILENAME'] = $temp_file; + + return $env; + } + + function _processUpload($section_text, $file) + { + if (array_key_exists('UPLOAD', $section_text) && !empty($section_text['UPLOAD'])) { + $upload_files = trim($section_text['UPLOAD']); + $upload_files = explode("\n", $upload_files); + + $request = "Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" . + "-----------------------------20896060251896012921717172737\n"; + foreach ($upload_files as $fileinfo) { + $fileinfo = explode('=', $fileinfo); + if (count($fileinfo) != 2) { + return PEAR::raiseError("Invalid UPLOAD section in test file: $file"); + } + if (!realpath(dirname($file) . '/' . $fileinfo[1])) { + return PEAR::raiseError("File for upload does not exist: $fileinfo[1] " . + "in test file: $file"); + } + $file_contents = file_get_contents(dirname($file) . '/' . $fileinfo[1]); + $fileinfo[1] = basename($fileinfo[1]); + $request .= "Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n"; + $request .= "Content-Type: text/plain\n\n"; + $request .= $file_contents . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + + if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + // encode POST raw + $post = trim($section_text['POST']); + $post = explode('&', $post); + foreach ($post as $i => $post_info) { + $post_info = explode('=', $post_info); + if (count($post_info) != 2) { + return PEAR::raiseError("Invalid POST data in test file: $file"); + } + $post_info[0] = rawurldecode($post_info[0]); + $post_info[1] = rawurldecode($post_info[1]); + $post[$i] = $post_info; + } + foreach ($post as $post_info) { + $request .= "Content-Disposition: form-data; name=\"$post_info[0]\"\n\n"; + $request .= $post_info[1] . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + unset($section_text['POST']); + } + $section_text['POST_RAW'] = $request; + } + + return $section_text; + } + + function _testCleanup($section_text, $temp_clean) + { + if ($section_text['CLEAN']) { + // perform test cleanup + $this->save_text($temp_clean, $section_text['CLEAN']); + $output = $this->system_with_timeout("$this->_php \"$temp_clean\" 2>&1"); + if (strlen($output[1])) { + echo "BORKED --CLEAN-- section! output:\n", $output[1]; + } + if (file_exists($temp_clean)) { + unlink($temp_clean); + } + } + } +} diff --git a/includes/pear/PEAR/Start.php b/includes/pear/PEAR/Start.php new file mode 100644 index 0000000..da69246 --- /dev/null +++ b/includes/pear/PEAR/Start.php @@ -0,0 +1,427 @@ +<?php +require_once 'PEAR.php'; +require_once 'System.php'; +require_once 'PEAR/Config.php'; +require_once 'PEAR/Command.php'; +require_once 'PEAR/Common.php'; +class PEAR_Start extends PEAR +{ + var $bin_dir; + var $data_dir; + var $cfg_dir; + var $www_dir; + var $install_pfc; + var $corePackages = + array( + 'Archive_Tar', + 'Console_Getopt', + 'PEAR', + 'Structures_Graph', + 'XML_Util', + ); + var $local_dir = array(); + var $origpwd; + var $pfc_packages = array( + 'DB', + 'Net_Socket', + 'Net_SMTP', + 'Mail', + 'XML_Parser', + 'XML_RPC', + 'PHPUnit' + ); + var $php_dir; + var $php_bin; + var $pear_conf; + var $validPHPBin = false; + var $test_dir; + var $download_dir; + var $temp_dir; + var $config = + array( + 'prefix', + 'bin_dir', + 'php_dir', + 'doc_dir', + 'data_dir', + 'cfg_dir', + 'www_dir', + 'test_dir', + 'temp_dir', + 'download_dir', + 'pear_conf', + ); + var $prefix; + var $progress = 0; + var $configPrompt = + array( + 'prefix' => 'Installation base ($prefix)', + 'temp_dir' => 'Temporary directory for processing', + 'download_dir' => 'Temporary directory for downloads', + 'bin_dir' => 'Binaries directory', + 'php_dir' => 'PHP code directory ($php_dir)', + 'doc_dir' => 'Documentation directory', + 'data_dir' => 'Data directory', + 'cfg_dir' => 'User-modifiable configuration files directory', + 'www_dir' => 'Public Web Files directory', + 'test_dir' => 'Tests directory', + 'pear_conf' => 'Name of configuration file', + ); + + var $localInstall; + var $PEARConfig; + var $tarball = array(); + + function PEAR_Start() + { + parent::PEAR(); + if (OS_WINDOWS) { + $this->configPrompt['php_bin'] = 'Path to CLI php.exe'; + $this->config[] = 'php_bin'; + $this->prefix = getcwd(); + + if (!@is_dir($this->prefix)) { + if (@is_dir('c:\php5')) { + $this->prefix = 'c:\php5'; + } elseif (@is_dir('c:\php4')) { + $this->prefix = 'c:\php4'; + } elseif (@is_dir('c:\php')) { + $this->prefix = 'c:\php'; + } + } + + $slash = "\\"; + if (strrpos($this->prefix, '\\') === (strlen($this->prefix) - 1)) { + $slash = ''; + } + + $this->localInstall = false; + $this->bin_dir = '$prefix'; + $this->temp_dir = '$prefix' . $slash . 'tmp'; + $this->download_dir = '$prefix' . $slash . 'tmp'; + $this->php_dir = '$prefix' . $slash . 'pear'; + $this->doc_dir = '$prefix' . $slash . 'docs'; + $this->data_dir = '$prefix' . $slash . 'data'; + $this->test_dir = '$prefix' . $slash . 'tests'; + $this->www_dir = '$prefix' . $slash . 'www'; + $this->cfg_dir = '$prefix' . $slash . 'cfg'; + $this->pear_conf = PEAR_CONFIG_SYSCONFDIR . '\\pear.ini'; + /* + * Detects php.exe + */ + $this->validPHPBin = true; + if ($t = $this->safeGetenv('PHP_PEAR_PHP_BIN')) { + $this->php_bin = dirname($t); + } elseif ($t = $this->safeGetenv('PHP_BIN')) { + $this->php_bin = dirname($t); + } elseif ($t = System::which('php')) { + $this->php_bin = dirname($t); + } elseif (is_file($this->prefix . '\cli\php.exe')) { + $this->php_bin = $this->prefix . '\cli'; + } elseif (is_file($this->prefix . '\php.exe')) { + $this->php_bin = $this->prefix; + } + $phpexe = OS_WINDOWS ? '\\php.exe' : '/php'; + if ($this->php_bin && !is_file($this->php_bin . $phpexe)) { + $this->php_bin = ''; + } else { + if (strpos($this->php_bin, ':') === 0) { + $this->php_bin = getcwd() . DIRECTORY_SEPARATOR . $this->php_bin; + } + } + if (!is_file($this->php_bin . $phpexe)) { + if (is_file('c:/php/cli/php.exe')) { + $this->php_bin = 'c"\\php\\cli'; + } elseif (is_file('c:/php5/php.exe')) { + $this->php_bin = 'c:\\php5'; + } elseif (is_file('c:/php4/cli/php.exe')) { + $this->php_bin = 'c:\\php4\\cli'; + } else { + $this->validPHPBin = false; + } + } + } else { + $this->prefix = dirname(PHP_BINDIR); + $this->pear_conf = PEAR_CONFIG_SYSCONFDIR . '/pear.conf'; + if (get_current_user() != 'root') { + $this->prefix = $this->safeGetenv('HOME') . '/pear'; + $this->pear_conf = $this->safeGetenv('HOME') . '.pearrc'; + } + $this->bin_dir = '$prefix/bin'; + $this->php_dir = '$prefix/share/pear'; + $this->temp_dir = '/tmp/pear/install'; + $this->download_dir = '/tmp/pear/install'; + $this->doc_dir = '$prefix/docs'; + $this->www_dir = '$prefix/www'; + $this->cfg_dir = '$prefix/cfg'; + $this->data_dir = '$prefix/data'; + $this->test_dir = '$prefix/tests'; + // check if the user has installed PHP with PHP or GNU layout + if (@is_dir("$this->prefix/lib/php/.registry")) { + $this->php_dir = '$prefix/lib/php'; + } elseif (@is_dir("$this->prefix/share/pear/lib/.registry")) { + $this->php_dir = '$prefix/share/pear/lib'; + $this->doc_dir = '$prefix/share/pear/docs'; + $this->data_dir = '$prefix/share/pear/data'; + $this->test_dir = '$prefix/share/pear/tests'; + } elseif (@is_dir("$this->prefix/share/php/.registry")) { + $this->php_dir = '$prefix/share/php'; + } + } + } + + function safeGetenv($var) + { + if (is_array($_ENV) && isset($_ENV[$var])) { + return $_ENV[$var]; + } + + return getenv($var); + } + + function show($stuff) + { + print $stuff; + } + + function locatePackagesToInstall() + { + $dp = @opendir(dirname(__FILE__) . '/go-pear-tarballs'); + if (empty($dp)) { + return PEAR::raiseError("while locating packages to install: opendir('" . + dirname(__FILE__) . "/go-pear-tarballs') failed"); + } + + $potentials = array(); + while (false !== ($entry = readdir($dp))) { + if ($entry{0} == '.' || !in_array(substr($entry, -4), array('.tar', '.tgz'))) { + continue; + } + $potentials[] = $entry; + } + + closedir($dp); + $notfound = array(); + foreach ($this->corePackages as $package) { + foreach ($potentials as $i => $candidate) { + if (preg_match('/^' . $package . '-' . _PEAR_COMMON_PACKAGE_VERSION_PREG + . '\.(tar|tgz)\\z/', $candidate)) { + $this->tarball[$package] = dirname(__FILE__) . '/go-pear-tarballs/' . $candidate; + unset($potentials[$i]); + continue 2; + } + } + + $notfound[] = $package; + } + + if (count($notfound)) { + return PEAR::raiseError("No tarballs found for core packages: " . + implode(', ', $notfound)); + } + + $this->tarball = array_merge($this->tarball, $potentials); + } + + function setupTempStuff() + { + if (!($this->ptmp = System::mktemp(array('-d')))) { + $this->show("System's Tempdir failed, trying to use \$prefix/tmp ..."); + $res = System::mkDir(array($this->prefix . '/tmp')); + if (!$res) { + return PEAR::raiseError('mkdir ' . $this->prefix . '/tmp ... failed'); + } + + $_temp = tempnam($this->prefix . '/tmp', 'gope'); + System::rm(array('-rf', $_temp)); + System::mkdir(array('-p','-m', '0700', $_temp)); + $this->ptmp = $this->prefix . '/tmp'; + $ok = @chdir($this->ptmp); + + if (!$ok) { // This should not happen, really ;) + $this->bail('chdir ' . $this->ptmp . ' ... failed'); + } + + print "ok\n"; + + // Adjust TEMPDIR envvars + if (!isset($_ENV)) { + $_ENV = array(); + }; + $_ENV['TMPDIR'] = $_ENV['TEMP'] = $this->prefix . '/tmp'; + } + + return @chdir($this->ptmp); + } + + /** + * Try to detect the kind of SAPI used by the + * the given php.exe. + * @author Pierrre-Alain Joye + */ + function win32DetectPHPSAPI() + { + if ($this->php_bin != '') { + if (OS_WINDOWS) { + exec('"' . $this->php_bin . '\\php.exe" -v', $res); + } else { + exec('"' . $this->php_bin . '/php" -v', $res); + } + + if (is_array($res)) { + if (isset($res[0]) && strpos($res[0],"(cli)")) { + return 'cli'; + } + + if (isset($res[0]) && strpos($res[0],"cgi")) { + return 'cgi'; + } + + if (isset($res[0]) && strpos($res[0],"cgi-fcgi")) { + return 'cgi'; + } + + return 'unknown'; + } + } + + return 'unknown'; + } + + function doInstall() + { + print "Beginning install...\n"; + // finish php_bin config + if (OS_WINDOWS) { + $this->php_bin .= '\\php.exe'; + } else { + $this->php_bin .= '/php'; + } + $this->PEARConfig = &PEAR_Config::singleton($this->pear_conf, $this->pear_conf); + $this->PEARConfig->set('preferred_state', 'stable'); + foreach ($this->config as $var) { + if ($var == 'pear_conf' || $var == 'prefix') { + continue; + } + $this->PEARConfig->set($var, $this->$var); + } + + $this->PEARConfig->store(); +// $this->PEARConfig->set('verbose', 6); + print "Configuration written to $this->pear_conf...\n"; + $this->registry = &$this->PEARConfig->getRegistry(); + print "Initialized registry...\n"; + $install = &PEAR_Command::factory('install', $this->PEARConfig); + print "Preparing to install...\n"; + $options = array( + 'nodeps' => true, + 'force' => true, + 'upgrade' => true, + ); + foreach ($this->tarball as $pkg => $src) { + print "installing $src...\n"; + } + $install->run('install', $options, array_values($this->tarball)); + } + + function postProcessConfigVars() + { + foreach ($this->config as $n => $var) { + for ($m = 1; $m <= count($this->config); $m++) { + $var2 = $this->config[$m]; + $this->$var = str_replace('$'.$var2, $this->$var2, $this->$var); + } + } + + foreach ($this->config as $var) { + $dir = $this->$var; + + if (!preg_match('/_dir\\z/', $var)) { + continue; + } + + if (!@is_dir($dir)) { + if (!System::mkDir(array('-p', $dir))) { + $root = OS_WINDOWS ? 'administrator' : 'root'; + return PEAR::raiseError("Unable to create {$this->configPrompt[$var]} $dir. +Run this script as $root or pick another location.\n"); + } + } + } + } + + /** + * Get the php.ini file used with the current + * process or with the given php.exe + * + * Horrible hack, but well ;) + * + * Not used yet, will add the support later + * @author Pierre-Alain Joye <paj@pearfr.org> + */ + function getPhpiniPath() + { + $pathIni = get_cfg_var('cfg_file_path'); + if ($pathIni && is_file($pathIni)) { + return $pathIni; + } + + // Oh well, we can keep this too :) + // I dunno if get_cfg_var() is safe on every OS + if (OS_WINDOWS) { + // on Windows, we can be pretty sure that there is a php.ini + // file somewhere + do { + $php_ini = PHP_CONFIG_FILE_PATH . DIRECTORY_SEPARATOR . 'php.ini'; + if (@file_exists($php_ini)) { + break; + } + $php_ini = 'c:\winnt\php.ini'; + if (@file_exists($php_ini)) { + break; + } + $php_ini = 'c:\windows\php.ini'; + } while (false); + } else { + $php_ini = PHP_CONFIG_FILE_PATH . DIRECTORY_SEPARATOR . 'php.ini'; + } + + if (@is_file($php_ini)) { + return $php_ini; + } + + // We re running in hackz&troubles :) + ob_implicit_flush(false); + ob_start(); + phpinfo(INFO_GENERAL); + $strInfo = ob_get_contents(); + ob_end_clean(); + ob_implicit_flush(true); + + if (php_sapi_name() != 'cli') { + $strInfo = strip_tags($strInfo,'<td>'); + $arrayInfo = explode("</td>", $strInfo ); + $cli = false; + } else { + $arrayInfo = explode("\n", $strInfo); + $cli = true; + } + + foreach ($arrayInfo as $val) { + if (strpos($val,"php.ini")) { + if ($cli) { + list(,$pathIni) = explode('=>', $val); + } else { + $pathIni = strip_tags(trim($val)); + } + $pathIni = trim($pathIni); + if (is_file($pathIni)) { + return $pathIni; + } + } + } + + return false; + } +} +?> diff --git a/includes/pear/PEAR/Start/CLI.php b/includes/pear/PEAR/Start/CLI.php new file mode 100644 index 0000000..a5ea9c5 --- /dev/null +++ b/includes/pear/PEAR/Start/CLI.php @@ -0,0 +1,616 @@ +<?php +require_once 'PEAR/Start.php'; +class PEAR_Start_CLI extends PEAR_Start +{ + + var $descLength; + var $descFormat; + var $first; + var $last; + var $origpwd; + var $tty; + + function PEAR_Start_CLI() + { + parent::PEAR_Start(); + ini_set('html_errors', 0); + define('WIN32GUI', OS_WINDOWS && php_sapi_name() == 'cli' && System::which('cscript')); + $this->tty = OS_WINDOWS ? @fopen('\con', 'r') : @fopen('/dev/tty', 'r'); + + if (!$this->tty) { + $this->tty = fopen('php://stdin', 'r'); + } + $this->origpwd = getcwd(); + $this->config = array_keys($this->configPrompt); + + // make indices run from 1... + array_unshift($this->config, ""); + unset($this->config[0]); + reset($this->config); + $this->descLength = max(array_map('strlen', $this->configPrompt)); + $this->descFormat = "%-{$this->descLength}s"; + $this->first = key($this->config); + end($this->config); + $this->last = key($this->config); + PEAR_Command::setFrontendType('CLI'); + } + + function _PEAR_Start_CLI() + { + if ($this->tty) { + @fclose($this->tty); + } + } + + function run() + { + if (PEAR::isError($err = $this->locatePackagesToInstall())) { + return $err; + } + $this->startupQuestion(); + $this->setupTempStuff(); + $this->getInstallLocations(); + $this->displayPreamble(); + if (PEAR::isError($err = $this->postProcessConfigVars())) { + return $err; + } + $this->doInstall(); + $this->finishInstall(); + } + + function startupQuestion() + { + if (OS_WINDOWS) { + print " +Are you installing a system-wide PEAR or a local copy? +(system|local) [system] : "; + $tmp = trim(fgets($this->tty, 1024)); + if (!empty($tmp) && strtolower($tmp) !== 'system') { + print "Please confirm local copy by typing 'yes' : "; + $tmp = trim(fgets($this->tty, 1024)); + if (strtolower($tmp) == 'yes') { + $slash = "\\"; + if (strrpos($this->prefix, '\\') === (strlen($this->prefix) - 1)) { + $slash = ''; + } + + $this->localInstall = true; + $this->pear_conf = '$prefix' . $slash . 'pear.ini'; + } + } + } else { + if (get_current_user() == 'root') { + return; + } + $this->pear_conf = $this->safeGetenv('HOME') . '/.pearrc'; + } + } + + function getInstallLocations() + { + while (true) { + print " +Below is a suggested file layout for your new PEAR installation. To +change individual locations, type the number in front of the +directory. Type 'all' to change all of them or simply press Enter to +accept these locations. + +"; + + foreach ($this->config as $n => $var) { + $fullvar = $this->$var; + foreach ($this->config as $blah => $unused) { + foreach ($this->config as $m => $var2) { + $fullvar = str_replace('$'.$var2, $this->$var2, $fullvar); + } + } + printf("%2d. $this->descFormat : %s\n", $n, $this->configPrompt[$var], $fullvar); + } + + print "\n$this->first-$this->last, 'all' or Enter to continue: "; + $tmp = trim(fgets($this->tty, 1024)); + if (empty($tmp)) { + if (OS_WINDOWS && !$this->validPHPBin) { + echo "**ERROR** +Please, enter the php.exe path. + +"; + } else { + break; + } + } + + if (isset($this->config[(int)$tmp])) { + $var = $this->config[(int)$tmp]; + $desc = $this->configPrompt[$var]; + $current = $this->$var; + if (WIN32GUI && $var != 'pear_conf'){ + $tmp = $this->win32BrowseForFolder("Choose a Folder for $desc [$current] :"); + $tmp.= '\\'; + } else { + print "(Use \$prefix as a shortcut for '$this->prefix', etc.) +$desc [$current] : "; + $tmp = trim(fgets($this->tty, 1024)); + } + $old = $this->$var; + $this->$var = $$var = $tmp; + if (OS_WINDOWS && $var=='php_bin') { + if ($this->validatePhpExecutable($tmp)) { + $this->php_bin = $tmp; + } else { + $this->php_bin = $old; + } + } + } elseif ($tmp == 'all') { + foreach ($this->config as $n => $var) { + $desc = $this->configPrompt[$var]; + $current = $this->$var; + print "$desc [$current] : "; + $tmp = trim(fgets($this->tty, 1024)); + if (!empty($tmp)) { + $this->$var = $tmp; + } + } + } + } + } + + function validatePhpExecutable($tmp) + { + if (OS_WINDOWS) { + if (strpos($tmp, 'php.exe')) { + $tmp = str_replace('php.exe', '', $tmp); + } + if (file_exists($tmp . DIRECTORY_SEPARATOR . 'php.exe')) { + $tmp = $tmp . DIRECTORY_SEPARATOR . 'php.exe'; + $this->php_bin_sapi = $this->win32DetectPHPSAPI(); + if ($this->php_bin_sapi=='cgi'){ + print " +****************************************************************************** +NOTICE! We found php.exe under $this->php_bin, it uses a $this->php_bin_sapi SAPI. +PEAR commandline tool works well with it. +If you have a CLI php.exe available, we recommend using it. + +Press Enter to continue..."; + $tmp = trim(fgets($this->tty, 1024)); + } elseif ($this->php_bin_sapi=='unknown') { + print " +****************************************************************************** +WARNING! We found php.exe under $this->php_bin, it uses an $this->php_bin_sapi SAPI. +PEAR commandline tool has NOT been tested with it. +If you have a CLI (or CGI) php.exe available, we strongly recommend using it. + +Press Enter to continue..."; + $tmp = trim(fgets($this->tty, 1024)); + } + echo "php.exe (sapi: $this->php_bin_sapi) found.\n\n"; + return $this->validPHPBin = true; + } else { + echo "**ERROR**: not a folder, or no php.exe found in this folder. +Press Enter to continue..."; + $tmp = trim(fgets($this->tty, 1024)); + return $this->validPHPBin = false; + } + } + } + + /** + * Create a vbs script to browse the getfolder dialog, called + * by cscript, if it's available. + * $label is the label text in the header of the dialog box + * + * TODO: + * - Do not show Control panel + * - Replace WSH with calls to w32 as soon as callbacks work + * @author Pierrre-Alain Joye + */ + function win32BrowseForFolder($label) + { + static $wshSaved=false; + static $cscript=''; + $wsh_browserfolder = 'Option Explicit +Dim ArgObj, var1, var2, sa, sFld +Set ArgObj = WScript.Arguments +Const BIF_EDITBOX = &H10 +Const BIF_NEWDIALOGSTYLE = &H40 +Const BIF_RETURNONLYFSDIRS = &H0001 +Const BIF_DONTGOBELOWDOMAIN = &H0002 +Const BIF_STATUSTEXT = &H0004 +Const BIF_RETURNFSANCESTORS = &H0008 +Const BIF_VALIDATE = &H0020 +Const BIF_BROWSEFORCOMPUTER = &H1000 +Const BIF_BROWSEFORPRINTER = &H2000 +Const BIF_BROWSEINCLUDEFILES = &H4000 +Const OFN_LONGNAMES = &H200000 +Const OFN_NOLONGNAMES = &H40000 +Const ssfDRIVES = &H11 +Const ssfNETWORK = &H12 +Set sa = CreateObject("Shell.Application") +var1=ArgObj(0) +Set sFld = sa.BrowseForFolder(0, var1, BIF_EDITBOX + BIF_VALIDATE + BIF_BROWSEINCLUDEFILES + BIF_RETURNFSANCESTORS+BIF_NEWDIALOGSTYLE , ssfDRIVES ) +if not sFld is nothing Then + if not left(sFld.items.item.path,1)=":" Then + WScript.Echo sFld.items.item.path + Else + WScript.Echo "invalid" + End If +Else + WScript.Echo "cancel" +End If +'; + if( !$wshSaved){ + $cscript = $this->ptmp . DIRECTORY_SEPARATOR . "bf.vbs"; + $fh = fopen($cscript, "wb+"); + fwrite($fh, $wsh_browserfolder, strlen($wsh_browserfolder)); + fclose($fh); + $wshSaved = true; + } + + exec('cscript ' . escapeshellarg($cscript) . ' "' . escapeshellarg($label) . '" //noLogo', $arPath); + if (!count($arPath) || $arPath[0]=='' || $arPath[0]=='cancel') { + return ''; + } elseif ($arPath[0]=='invalid') { + echo "Invalid Path.\n"; + return ''; + } + + @unlink($cscript); + return $arPath[0]; + } + + function displayPreamble() + { + if (OS_WINDOWS) { + /* + * Checks PHP SAPI version under windows/CLI + */ + if ($this->php_bin == '') { + print " +We do not find any php.exe, please select the php.exe folder (CLI is +recommended, usually in c:\php\cli\php.exe) +"; + $this->validPHPBin = false; + } elseif (strlen($this->php_bin)) { + $this->php_bin_sapi = $this->win32DetectPHPSAPI(); + $this->validPHPBin = true; + switch ($this->php_bin_sapi) { + case 'cli': + break; + case 'cgi': + case 'cgi-fcgi': + print " +*NOTICE* +We found php.exe under $this->php_bin, it uses a $this->php_bin_sapi SAPI. PEAR commandline +tool works well with it, if you have a CLI php.exe available, we +recommend using it. +"; + break; + default: + print " +*WARNING* +We found php.exe under $this->php_bin, it uses an unknown SAPI. PEAR commandline +tool has not been tested with it, if you have a CLI (or CGI) php.exe available, +we strongly recommend using it. + +"; + break; + } + } + } + } + + function finishInstall() + { + $sep = OS_WINDOWS ? ';' : ':'; + $include_path = explode($sep, ini_get('include_path')); + if (OS_WINDOWS) { + $found = false; + $t = strtolower($this->php_dir); + foreach ($include_path as $path) { + if ($t == strtolower($path)) { + $found = true; + break; + } + } + } else { + $found = in_array($this->php_dir, $include_path); + } + if (!$found) { + print " +****************************************************************************** +WARNING! The include_path defined in the currently used php.ini does not +contain the PEAR PHP directory you just specified: +<$this->php_dir> +If the specified directory is also not in the include_path used by +your scripts, you will have problems getting any PEAR packages working. +"; + + if ($php_ini = $this->getPhpiniPath()) { + print "\n\nWould you like to alter php.ini <$php_ini>? [Y/n] : "; + $alter_phpini = !stristr(fgets($this->tty, 1024), "n"); + if ($alter_phpini) { + $this->alterPhpIni($php_ini); + } else { + if (OS_WINDOWS) { + print " +Please look over your php.ini file to make sure +$this->php_dir is in your include_path."; + } else { + print " +I will add a workaround for this in the 'pear' command to make sure +the installer works, but please look over your php.ini or Apache +configuration to make sure $this->php_dir is in your include_path. +"; + } + } + } + + print " +Current include path : ".ini_get('include_path')." +Configured directory : $this->php_dir +Currently used php.ini (guess) : $php_ini +"; + + print "Press Enter to continue: "; + fgets($this->tty, 1024); + } + + $pear_cmd = $this->bin_dir . DIRECTORY_SEPARATOR . 'pear'; + $pear_cmd = OS_WINDOWS ? strtolower($pear_cmd).'.bat' : $pear_cmd; + + // check that the installed pear and the one in the path are the same (if any) + $pear_old = System::which(OS_WINDOWS ? 'pear.bat' : 'pear', $this->bin_dir); + if ($pear_old && ($pear_old != $pear_cmd)) { + // check if it is a link or symlink + $islink = OS_WINDOWS ? false : is_link($pear_old) ; + if ($islink && readlink($pear_old) != $pear_cmd) { + print "\n** WARNING! The link $pear_old does not point to the " . + "installed $pear_cmd\n"; + } elseif (!$this->localInstall && is_writable($pear_old) && !is_dir($pear_old)) { + rename($pear_old, "{$pear_old}_old"); + print "\n** WARNING! Backed up old pear to {$pear_old}_old\n"; + } else { + print "\n** WARNING! Old version found at $pear_old, please remove it or ". + "be sure to use the new $pear_cmd command\n"; + } + } + + print "\nThe 'pear' command is now at your service at $pear_cmd\n"; + + // Alert the user if the pear cmd is not in PATH + $old_dir = $pear_old ? dirname($pear_old) : false; + if (!$this->which('pear', $old_dir)) { + print " +** The 'pear' command is not currently in your PATH, so you need to +** use '$pear_cmd' until you have added +** '$this->bin_dir' to your PATH environment variable. + +"; + + print "Run it without parameters to see the available actions, try 'pear list' +to see what packages are installed, or 'pear help' for help. + +For more information about PEAR, see: + + http://pear.php.net/faq.php + http://pear.php.net/manual/ + +Thanks for using go-pear! + +"; + } + + if (OS_WINDOWS && !$this->localInstall) { + $this->win32CreateRegEnv(); + } + } + + /** + * System::which() does not allow path exclusion + */ + function which($program, $dont_search_in = false) + { + if (OS_WINDOWS) { + if ($_path = $this->safeGetEnv('Path')) { + $dirs = explode(';', $_path); + } else { + $dirs = explode(';', $this->safeGetEnv('PATH')); + } + foreach ($dirs as $i => $dir) { + $dirs[$i] = strtolower(realpath($dir)); + } + if ($dont_search_in) { + $dont_search_in = strtolower(realpath($dont_search_in)); + } + if ($dont_search_in && + ($key = array_search($dont_search_in, $dirs)) !== false) + { + unset($dirs[$key]); + } + + foreach ($dirs as $dir) { + $dir = str_replace('\\\\', '\\', $dir); + if (!strlen($dir)) { + continue; + } + if ($dir{strlen($dir) - 1} != '\\') { + $dir .= '\\'; + } + $tmp = $dir . $program; + $info = pathinfo($tmp); + if (isset($info['extension']) && in_array(strtolower($info['extension']), + array('exe', 'com', 'bat', 'cmd'))) { + if (file_exists($tmp)) { + return strtolower($tmp); + } + } elseif (file_exists($ret = $tmp . '.exe') || + file_exists($ret = $tmp . '.com') || + file_exists($ret = $tmp . '.bat') || + file_exists($ret = $tmp . '.cmd')) { + return strtolower($ret); + } + } + } else { + $dirs = explode(':', $this->safeGetEnv('PATH')); + if ($dont_search_in && + ($key = array_search($dont_search_in, $dirs)) !== false) + { + unset($dirs[$key]); + } + foreach ($dirs as $dir) { + if (is_executable("$dir/$program")) { + return "$dir/$program"; + } + } + } + return false; + } + + /** + * Not optimized, but seems to work, if some nice + * peardev will test it? :) + * + * @author Pierre-Alain Joye <paj@pearfr.org> + */ + function alterPhpIni($pathIni='') + { + $foundAt = array(); + $iniSep = OS_WINDOWS ? ';' : ':'; + + if ($pathIni=='') { + $pathIni = $this->getPhpiniPath(); + } + + $arrayIni = file($pathIni); + $i=0; + $found=0; + + // Looks for each active include_path directives + foreach ($arrayIni as $iniLine) { + $iniLine = trim($iniLine); + $iniLine = str_replace(array("\n", "\r"), array('', ''), $iniLine); + if (preg_match("/^\s*include_path/", $iniLine)) { + $foundAt[] = $i; + $found++; + } + $i++; + } + + if ($found) { + $includeLine = $arrayIni[$foundAt[0]]; + list(, $currentPath) = explode('=', $includeLine); + + $currentPath = trim($currentPath); + if (substr($currentPath,0,1) == '"') { + $currentPath = substr($currentPath, 1, strlen($currentPath) - 2); + } + + $arrayPath = explode($iniSep, $currentPath); + $newPath = array(); + if ($arrayPath[0]=='.') { + $newPath[0] = '.'; + $newPath[1] = $this->php_dir; + array_shift($arrayPath); + } else { + $newPath[0] = $this->php_dir; + } + + foreach ($arrayPath as $path) { + $newPath[]= $path; + } + } else { + $newPath = array(); + $newPath[0] = '.'; + $newPath[1] = $this->php_dir; + $foundAt[] = count($arrayIni); // add a new line if none is present + } + $nl = OS_WINDOWS ? "\r\n" : "\n"; + $includepath = 'include_path="' . implode($iniSep,$newPath) . '"'; + $newInclude = "$nl$nl;***** Added by go-pear$nl" . + $includepath . + $nl . ";*****" . + $nl . $nl; + + $arrayIni[$foundAt[0]] = $newInclude; + + for ($i=1; $i<$found; $i++) { + $arrayIni[$foundAt[$i]]=';' . trim($arrayIni[$foundAt[$i]]); + } + + $newIni = implode("", $arrayIni); + if (!($fh = @fopen($pathIni, "wb+"))) { + $prefixIni = $this->prefix . DIRECTORY_SEPARATOR . "php.ini-gopear"; + $fh = fopen($prefixIni, "wb+"); + if (!$fh) { + echo " +****************************************************************************** +WARNING: Cannot write to $pathIni nor in $this->prefix/php.ini-gopear. Please +modify manually your php.ini by adding: + +$includepath + +"; + return false; + } else { + fwrite($fh, $newIni, strlen($newIni)); + fclose($fh); + echo " +****************************************************************************** +WARNING: Cannot write to $pathIni, but php.ini was successfully created +at <$this->prefix/php.ini-gopear>. Please replace the file <$pathIni> with +<$prefixIni> or modify your php.ini by adding: + +$includepath + +"; + + } + } else { + fwrite($fh, $newIni, strlen($newIni)); + fclose($fh); + echo " +php.ini <$pathIni> include_path updated. +"; + } + return true; + } + + /** + * Generates a registry addOn for Win32 platform + * This addon set PEAR environment variables + * @author Pierrre-Alain Joye + */ + function win32CreateRegEnv() + { + $nl = "\r\n"; + $reg ='REGEDIT4'.$nl. + '[HKEY_CURRENT_USER\Environment]'. $nl . + '"PHP_PEAR_SYSCONF_DIR"="' . addslashes($this->prefix) . '"' . $nl . + '"PHP_PEAR_INSTALL_DIR"="' . addslashes($this->php_dir) . '"' . $nl . + '"PHP_PEAR_DOC_DIR"="' . addslashes($this->doc_dir) . '"' . $nl . + '"PHP_PEAR_BIN_DIR"="' . addslashes($this->bin_dir) . '"' . $nl . + '"PHP_PEAR_DATA_DIR"="' . addslashes($this->data_dir) . '"' . $nl . + '"PHP_PEAR_PHP_BIN"="' . addslashes($this->php_bin) . '"' . $nl . + '"PHP_PEAR_TEST_DIR"="' . addslashes($this->test_dir) . '"' . $nl; + + $fh = fopen($this->prefix . DIRECTORY_SEPARATOR . 'PEAR_ENV.reg', 'wb'); + if($fh){ + fwrite($fh, $reg, strlen($reg)); + fclose($fh); + echo " + +* WINDOWS ENVIRONMENT VARIABLES * +For convenience, a REG file is available under {$this->prefix}PEAR_ENV.reg . +This file creates ENV variables for the current user. + +Double-click this file to add it to the current user registry. + +"; + } + } + + function displayHTMLProgress() + { + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Common.php b/includes/pear/PEAR/Task/Common.php new file mode 100644 index 0000000..0ca5ccf --- /dev/null +++ b/includes/pear/PEAR/Task/Common.php @@ -0,0 +1,202 @@ +<?php +/** + * PEAR_Task_Common, base class for installer tasks + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Error codes for task validation routines + */ +define('PEAR_TASK_ERROR_NOATTRIBS', 1); +define('PEAR_TASK_ERROR_MISSING_ATTRIB', 2); +define('PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE', 3); +define('PEAR_TASK_ERROR_INVALID', 4); +/**#@-*/ +define('PEAR_TASK_PACKAGE', 1); +define('PEAR_TASK_INSTALL', 2); +define('PEAR_TASK_PACKAGEANDINSTALL', 3); +/** + * A task is an operation that manipulates the contents of a file. + * + * Simple tasks operate on 1 file. Multiple tasks are executed after all files have been + * processed and installed, and are designed to operate on all files containing the task. + * The Post-install script task simply takes advantage of the fact that it will be run + * after installation, replace is a simple task. + * + * Combining tasks is possible, but ordering is significant. + * + * <file name="test.php" role="php"> + * <tasks:replace from="@data-dir@" to="data_dir" type="pear-config"/> + * <tasks:postinstallscript/> + * </file> + * + * This will first replace any instance of @data-dir@ in the test.php file + * with the path to the current data directory. Then, it will include the + * test.php file and run the script it contains to configure the package post-installation. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @abstract + */ +class PEAR_Task_Common +{ + /** + * Valid types for this version are 'simple' and 'multiple' + * + * - simple tasks operate on the contents of a file and write out changes to disk + * - multiple tasks operate on the contents of many files and write out the + * changes directly to disk + * + * Child task classes must override this property. + * @access protected + */ + var $type = 'simple'; + /** + * Determines which install phase this task is executed under + */ + var $phase = PEAR_TASK_INSTALL; + /** + * @access protected + */ + var $config; + /** + * @access protected + */ + var $registry; + /** + * @access protected + */ + var $logger; + /** + * @access protected + */ + var $installphase; + /** + * @param PEAR_Config + * @param PEAR_Common + */ + function PEAR_Task_Common(&$config, &$logger, $phase) + { + $this->config = &$config; + $this->registry = &$config->getRegistry(); + $this->logger = &$logger; + $this->installphase = $phase; + if ($this->type == 'multiple') { + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][get_class($this)][] = &$this; + } + } + + /** + * Validate the basic contents of a task tag. + * @param PEAR_PackageFile_v2 + * @param array + * @param PEAR_Config + * @param array the entire parsed <file> tag + * @return true|array On error, return an array in format: + * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...]) + * + * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in + * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array + * of legal values in + * @static + * @abstract + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the <file> tag containing this task + * @param string|null last installed version of this package + * @abstract + */ + function init($xml, $fileAttributes, $lastVersion) + { + } + + /** + * Begin a task processing session. All multiple tasks will be processed after each file + * has been successfully installed, all simple tasks should perform their task here and + * return any errors using the custom throwError() method to allow forward compatibility + * + * This method MUST NOT write out any changes to disk + * @param PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + * @abstract + */ + function startSession($pkg, $contents, $dest) + { + } + + /** + * This method is used to process each of the tasks for a particular multiple class + * type. Simple tasks need not implement this method. + * @param array an array of tasks + * @access protected + * @static + * @abstract + */ + function run($tasks) + { + } + + /** + * @static + * @final + */ + function hasPostinstallTasks() + { + return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * @static + * @final + */ + function runPostinstallTasks() + { + foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) { + $err = call_user_func(array($class, 'run'), + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]); + if ($err) { + return PEAR_Task_Common::throwError($err); + } + } + unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * Determines whether a role is a script + * @return bool + */ + function isScript() + { + return $this->type == 'script'; + } + + function throwError($msg, $code = -1) + { + include_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Postinstallscript.php b/includes/pear/PEAR/Task/Postinstallscript.php new file mode 100644 index 0000000..6281340 --- /dev/null +++ b/includes/pear/PEAR/Task/Postinstallscript.php @@ -0,0 +1,323 @@ +<?php +/** + * <tasks:postinstallscript> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the postinstallscript file task. + * + * Note that post-install scripts are handled separately from installation, by the + * "pear run-scripts" command + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Postinstallscript extends PEAR_Task_Common +{ + var $type = 'script'; + var $_class; + var $_params; + var $_obj; + /** + * + * @var PEAR_PackageFile_v2 + */ + var $_pkg; + var $_contents; + var $phase = PEAR_TASK_INSTALL; + + /** + * Validate the raw xml at parsing-time. + * + * This also attempts to validate the script to make sure it meets the criteria + * for a post-install script + * @param PEAR_PackageFile_v2 + * @param array The XML contents of the <postinstallscript> tag + * @param PEAR_Config + * @param array the entire parsed <file> tag + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($fileXml['role'] != 'php') { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must be role="php"'); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $file = $pkg->getFileContents($fileXml['name']); + if (PEAR::isError($file)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" is not valid: ' . + $file->getMessage()); + } elseif ($file === null) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" could not be retrieved for processing!'); + } else { + $analysis = $pkg->analyzeSourceCode($file, true); + if (!$analysis) { + PEAR::popErrorHandling(); + $warnings = ''; + foreach ($pkg->getValidationWarnings() as $warn) { + $warnings .= $warn['message'] . "\n"; + } + return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' . + $fileXml['name'] . '" failed: ' . $warnings); + } + if (count($analysis['declared_classes']) != 1) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare exactly 1 class'); + } + $class = $analysis['declared_classes'][0]; + if ($class != str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall') { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" class "' . $class . '" must be named "' . + str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall"'); + } + if (!isset($analysis['declared_methods'][$class])) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + $methods = array('init' => 0, 'run' => 1); + foreach ($analysis['declared_methods'][$class] as $method) { + if (isset($methods[$method])) { + unset($methods[$method]); + } + } + if (count($methods)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + } + PEAR::popErrorHandling(); + $definedparams = array(); + $tasksNamespace = $pkg->getTasksNs() . ':'; + if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) { + // in order to support the older betas, which did not expect internal tags + // to also use the namespace + $tasksNamespace = ''; + } + if (isset($xml[$tasksNamespace . 'paramgroup'])) { + $params = $xml[$tasksNamespace . 'paramgroup']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + foreach ($params as $param) { + if (!isset($param[$tasksNamespace . 'id'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" <paramgroup> must have ' . + 'an ' . $tasksNamespace . 'id> tag'); + } + if (isset($param[$tasksNamespace . 'name'])) { + if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" parameter "' . $param[$tasksNamespace . 'name'] . + '" has not been previously defined'); + } + if (!isset($param[$tasksNamespace . 'conditiontype'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!in_array($param[$tasksNamespace . 'conditiontype'], + array('=', '!=', 'preg_match'))) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!isset($param[$tasksNamespace . 'value'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'value> tag containing expected parameter value'); + } + } + if (isset($param[$tasksNamespace . 'instructions'])) { + if (!is_string($param[$tasksNamespace . 'instructions'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" ' . $tasksNamespace . 'instructions> must be simple text'); + } + } + if (!isset($param[$tasksNamespace . 'param'])) { + continue; // <param> is no longer required + } + $subparams = $param[$tasksNamespace . 'param']; + if (!is_array($subparams) || !isset($subparams[0])) { + $subparams = array($subparams); + } + foreach ($subparams as $subparam) { + if (!isset($subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter for ' . + $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . '" must have ' . + 'a ' . $tasksNamespace . 'name> tag'); + } + if (!preg_match('/[a-zA-Z0-9]+/', + $subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" is not a valid name. Must contain only alphanumeric characters'); + } + if (!isset($subparam[$tasksNamespace . 'prompt'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'prompt> tag'); + } + if (!isset($subparam[$tasksNamespace . 'type'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'type> tag'); + } + $definedparams[] = $param[$tasksNamespace . 'id'] . '::' . + $subparam[$tasksNamespace . 'name']; + } + } + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the <file> tag containing this task + * @param string|null last installed version of this package, if any (useful for upgrades) + */ + function init($xml, $fileattribs, $lastversion) + { + $this->_class = str_replace('/', '_', $fileattribs['name']); + $this->_filename = $fileattribs['name']; + $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall'; + $this->_params = $xml; + $this->_lastversion = $lastversion; + } + + /** + * Strip the tasks: namespace from internal params + * + * @access private + */ + function _stripNamespace($params = null) + { + if ($params === null) { + $params = array(); + if (!is_array($this->_params)) { + return; + } + foreach ($this->_params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + $this->_params = $params; + } else { + $newparams = array(); + foreach ($params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + return $newparams; + } + } + + /** + * Unlike other tasks, the installed file name is passed in instead of the file contents, + * because this task is handled post-installation + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file name + * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError) + */ + function startSession($pkg, $contents) + { + if ($this->installphase != PEAR_TASK_INSTALL) { + return false; + } + // remove the tasks: namespace if present + $this->_pkg = $pkg; + $this->_stripNamespace(); + $this->logger->log(0, 'Including external post-installation script "' . + $contents . '" - any errors are in this script'); + include_once $contents; + if (class_exists($this->_class)) { + $this->logger->log(0, 'Inclusion succeeded'); + } else { + return $this->throwError('init of post-install script class "' . $this->_class + . '" failed'); + } + $this->_obj = new $this->_class; + $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $res = $this->_obj->init($this->config, $pkg, $this->_lastversion); + PEAR::popErrorHandling(); + if ($res) { + $this->logger->log(0, 'init succeeded'); + } else { + return $this->throwError('init of post-install script "' . $this->_class . + '->init()" failed'); + } + $this->_contents = $contents; + return true; + } + + /** + * No longer used + * @see PEAR_PackageFile_v2::runPostinstallScripts() + * @param array an array of tasks + * @param string install or upgrade + * @access protected + * @static + */ + function run() + { + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Postinstallscript/rw.php b/includes/pear/PEAR/Task/Postinstallscript/rw.php new file mode 100644 index 0000000..c48db6c --- /dev/null +++ b/includes/pear/PEAR/Task/Postinstallscript/rw.php @@ -0,0 +1,169 @@ +<?php +/** + * <tasks:postinstallscript> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Postinstallscript.php'; +/** + * Abstracts the postinstallscript file task xml. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript +{ + /** + * parent package file object + * + * @var PEAR_PackageFile_v2_rw + */ + var $_pkg; + /** + * Enter description here... + * + * @param PEAR_PackageFile_v2_rw $pkg + * @param PEAR_Config $config + * @param PEAR_Frontend $logger + * @param array $fileXml + * @return PEAR_Task_Postinstallscript_rw + */ + function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function getName() + { + return 'postinstallscript'; + } + + /** + * add a simple <paramgroup> to the post-install script + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addConditionTypeGroup()} to add a <paramgroup> containing + * a <conditiontype> tag + * @param string $id <paramgroup> id as seen by the script + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addParamGroup($id, $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = + array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + /** + * add a complex <paramgroup> to the post-install script with conditions + * + * This inserts a <paramgroup> with + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addParamGroup()} to add a simple <paramgroup> + * + * @param string $id <paramgroup> id as seen by the script + * @param string $oldgroup <paramgroup> id of the section referenced by + * <conditiontype> + * @param string $param name of the <param> from the older section referenced + * by <contitiontype> + * @param string $value value to match of the parameter + * @param string $conditiontype one of '=', '!=', 'preg_match' + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=', + $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param; + $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype; + $stuff[$this->_pkg->getTasksNs() . ':value'] = $value; + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + function getXml() + { + return $this->_params; + } + + /** + * Use to set up a param tag for use in creating a paramgroup + * @static + */ + function getParam($name, $prompt, $type = 'string', $default = null) + { + if ($default !== null) { + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + $this->_pkg->getTasksNs() . ':default' => $default + ); + } + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + ); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Replace.php b/includes/pear/PEAR/Task/Replace.php new file mode 100644 index 0000000..4a36b96 --- /dev/null +++ b/includes/pear/PEAR/Task/Replace.php @@ -0,0 +1,176 @@ +<?php +/** + * <tasks:replace> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the replace file task. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Replace extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGEANDINSTALL; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if (!isset($xml['attribs'])) { + return array(PEAR_TASK_ERROR_NOATTRIBS); + } + if (!isset($xml['attribs']['type'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'type'); + } + if (!isset($xml['attribs']['to'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'to'); + } + if (!isset($xml['attribs']['from'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'from'); + } + if ($xml['attribs']['type'] == 'pear-config') { + if (!in_array($xml['attribs']['to'], $config->getKeys())) { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + $config->getKeys()); + } + } elseif ($xml['attribs']['type'] == 'php-const') { + if (defined($xml['attribs']['to'])) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('valid PHP constant')); + } + } elseif ($xml['attribs']['type'] == 'package-info') { + if (in_array($xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time'))) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time')); + } + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'], + array('pear-config', 'package-info', 'php-const')); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml; + } + + /** + * Do a package.xml 1.0 replacement, with additional package-info fields available + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $subst_from = $subst_to = array(); + foreach ($this->_replacements as $a) { + $a = $a['attribs']; + $to = ''; + if ($a['type'] == 'pear-config') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if ($a['to'] == 'master_server') { + $chan = $this->registry->getChannel($pkg->getChannel()); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } else { + if ($this->config->isDefinedLayer('ftp')) { + // try the remote config file first + $to = $this->config->get($a['to'], 'ftp', $pkg->getChannel()); + if (is_null($to)) { + // then default to local + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } else { + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } + if (is_null($to)) { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } elseif ($a['type'] == 'php-const') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if (defined($a['to'])) { + $to = constant($a['to']); + } else { + $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]"); + return false; + } + } else { + if ($t = $pkg->packageInfo($a['to'])) { + $to = $t; + } else { + $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]"); + return false; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + $this->logger->log(3, "doing " . sizeof($subst_from) . + " substitution(s) for $dest"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + return $contents; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Replace/rw.php b/includes/pear/PEAR/Task/Replace/rw.php new file mode 100644 index 0000000..28496d1 --- /dev/null +++ b/includes/pear/PEAR/Task/Replace/rw.php @@ -0,0 +1,61 @@ +<?php +/** + * <tasks:replace> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Replace.php'; +/** + * Abstracts the replace task xml. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Replace_rw extends PEAR_Task_Replace +{ + function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function setInfo($from, $to, $type) + { + $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type)); + } + + function getName() + { + return 'replace'; + } + + function getXml() + { + return $this->_params; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Unixeol.php b/includes/pear/PEAR/Task/Unixeol.php new file mode 100644 index 0000000..2c20313 --- /dev/null +++ b/includes/pear/PEAR/Task/Unixeol.php @@ -0,0 +1,77 @@ +<?php +/** + * <tasks:unixeol> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the unix line endings file task. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Unixeol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with line endings customized for the current OS + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Unixeol/rw.php b/includes/pear/PEAR/Task/Unixeol/rw.php new file mode 100644 index 0000000..5348fac --- /dev/null +++ b/includes/pear/PEAR/Task/Unixeol/rw.php @@ -0,0 +1,56 @@ +<?php +/** + * <tasks:unixeol> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Unixeol.php'; +/** + * Abstracts the unixeol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol +{ + function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'unixeol'; + } + + function getXml() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Windowseol.php b/includes/pear/PEAR/Task/Windowseol.php new file mode 100644 index 0000000..d2bd2c8 --- /dev/null +++ b/includes/pear/PEAR/Task/Windowseol.php @@ -0,0 +1,77 @@ +<?php +/** + * <tasks:windowseol> + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the windows line endsings file task. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Windowseol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with windows line endings + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\r\\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Task/Windowseol/rw.php b/includes/pear/PEAR/Task/Windowseol/rw.php new file mode 100644 index 0000000..e86e4f0 --- /dev/null +++ b/includes/pear/PEAR/Task/Windowseol/rw.php @@ -0,0 +1,56 @@ +<?php +/** + * <tasks:windowseol> - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Windowseol.php'; +/** + * Abstracts the windowseol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol +{ + function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'windowseol'; + } + + function getXml() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Validate.php b/includes/pear/PEAR/Validate.php new file mode 100644 index 0000000..3c2471b --- /dev/null +++ b/includes/pear/PEAR/Validate.php @@ -0,0 +1,629 @@ +<?php +/** + * PEAR_Validate + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Constants for install stage + */ +define('PEAR_VALIDATE_INSTALLING', 1); +define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others +define('PEAR_VALIDATE_NORMAL', 3); +define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others +define('PEAR_VALIDATE_PACKAGING', 7); +/**#@-*/ +require_once 'PEAR/Common.php'; +require_once 'PEAR/Validator/PECL.php'; + +/** + * Validation class for package.xml - channel-level advanced validation + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Validate +{ + var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + var $_packagexml; + /** + * @var int one of the PEAR_VALIDATE_* constants + */ + var $_state = PEAR_VALIDATE_NORMAL; + /** + * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same) + * @var array + * @access private + */ + var $_failures = array('error' => array(), 'warning' => array()); + + /** + * Override this method to handle validation of normal package names + * @param string + * @return bool + * @access protected + */ + function _validPackageName($name) + { + return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name); + } + + /** + * @param string package name to validate + * @param string name of channel-specific validation package + * @final + */ + function validPackageName($name, $validatepackagename = false) + { + if ($validatepackagename) { + if (strtolower($name) == strtolower($validatepackagename)) { + return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name); + } + } + return $this->_validPackageName($name); + } + + /** + * This validates a bundle name, and bundle names must conform + * to the PEAR naming convention, so the method is final and static. + * @param string + * @final + * @static + */ + function validGroupName($name) + { + return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name); + } + + /** + * Determine whether $state represents a valid stability level + * @param string + * @return bool + * @static + * @final + */ + function validState($state) + { + return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable')); + } + + /** + * Get a list of valid stability levels + * @return array + * @static + * @final + */ + function getValidStates() + { + return array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + } + + /** + * Determine whether a version is a properly formatted version number that can be used + * by version_compare + * @param string + * @return bool + * @static + * @final + */ + function validVersion($ver) + { + return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function setPackageFile(&$pf) + { + $this->_packagexml = &$pf; + } + + /** + * @access private + */ + function _addFailure($field, $reason) + { + $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason); + } + + /** + * @access private + */ + function _addWarning($field, $reason) + { + $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason); + } + + function getFailures() + { + $failures = $this->_failures; + $this->_failures = array('warnings' => array(), 'errors' => array()); + return $failures; + } + + /** + * @param int one of the PEAR_VALIDATE_* constants + */ + function validate($state = null) + { + if (!isset($this->_packagexml)) { + return false; + } + if ($state !== null) { + $this->_state = $state; + } + $this->_failures = array('warnings' => array(), 'errors' => array()); + $this->validatePackageName(); + $this->validateVersion(); + $this->validateMaintainers(); + $this->validateDate(); + $this->validateSummary(); + $this->validateDescription(); + $this->validateLicense(); + $this->validateNotes(); + if ($this->_packagexml->getPackagexmlVersion() == '1.0') { + $this->validateState(); + $this->validateFilelist(); + } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') { + $this->validateTime(); + $this->validateStability(); + $this->validateDeps(); + $this->validateMainFilelist(); + $this->validateReleaseFilelist(); + //$this->validateGlobalTasks(); + $this->validateChangelog(); + } + return !((bool) count($this->_failures['errors'])); + } + + /** + * @access protected + */ + function validatePackageName() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING || + $this->_state == PEAR_VALIDATE_NORMAL) { + if (($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') && + $this->_packagexml->getExtends()) { + $version = $this->_packagexml->getVersion() . ''; + $name = $this->_packagexml->getPackage(); + $test = array_shift($a = explode('.', $version)); + if ($test == '0') { + return true; + } + $vlen = strlen($test); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if ($majver != $test) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name should ' . + 'have a postfix equal to the major version like "' . + $this->_packagexml->getExtends() . $test . '"'); + return true; + } elseif (substr($name, 0, strlen($name) - $vlen) != + $this->_packagexml->getExtends()) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name must ' . + 'be an extension like "' . $this->_packagexml->getExtends() . + $test . '"'); + return true; + } + } + } + if (!$this->validPackageName($this->_packagexml->getPackage())) { + $this->_addFailure('name', 'package name "' . + $this->_packagexml->getPackage() . '" is invalid'); + return false; + } + } + + /** + * @access protected + */ + function validateVersion() + { + if ($this->_state != PEAR_VALIDATE_PACKAGING) { + if (!$this->validVersion($this->_packagexml->getVersion())) { + $this->_addFailure('version', + 'Invalid version number "' . $this->_packagexml->getVersion() . '"'); + } + return false; + } + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + if (count($versioncomponents) != 3) { + $this->_addWarning('version', + 'A version number should have 3 decimals (x.y.z)'); + return true; + } + $name = $this->_packagexml->getPackage(); + // version must be based upon state + switch ($this->_packagexml->getState()) { + case 'snapshot' : + return true; + case 'devel' : + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } else { + $this->_addWarning('version', + 'packages with devel stability must be < version 1.0.0'); + } + return true; + break; + case 'alpha' : + case 'beta' : + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + if (substr($versioncomponents[2], 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions ' . + 'must have capital RC, not lower-case rc'); + return false; + } + } + if (!$this->_packagexml->getExtends()) { + if ($versioncomponents[0] == '1') { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 1.*.0000 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 1.*.0RC1 or 1.*.0beta24 etc. + return true; + } else { + // version 1.*.0 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + 'bugfix versions (1.3.x where x > 0) probably should ' . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + 'major versions greater than 1 are not allowed for packages ' . + 'without an <extends> tag or an identical postfix (foo2 v2.0.0)'); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } else { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + if ($versioncomponents[0] == $majver) { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 2.*.0000 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return false; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 2.*.0RC1 or 2.*.0beta24 etc. + return true; + } else { + // version 2.*.0 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 cannot be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + "bugfix versions ($majver.x.y where y > 0) should " . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases"); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } + return true; + break; + case 'stable' : + if ($versioncomponents[0] == '0') { + $this->_addWarning('version', 'versions less than 1.0.0 cannot ' . + 'be stable'); + return true; + } + if (!is_numeric($versioncomponents[2])) { + if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i', + $versioncomponents[2])) { + $this->_addWarning('version', 'version "' . $version . '" or any ' . + 'RC/beta/alpha version cannot be stable'); + return true; + } + } + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_packagexml->getExtends()) { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + } elseif ($versioncomponents[0] > 1) { + $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' . + '1 for any package that does not have an <extends> tag'); + } + return true; + break; + default : + return false; + break; + } + } + + /** + * @access protected + */ + function validateMaintainers() + { + // maintainers can only be truly validated server-side for most channels + // but allow this customization for those who wish it + return true; + } + + /** + * @access protected + */ + function validateDate() + { + if ($this->_state == PEAR_VALIDATE_NORMAL || + $this->_state == PEAR_VALIDATE_PACKAGING) { + + if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/', + $this->_packagexml->getDate(), $res) || + count($res) < 4 + || !checkdate($res[2], $res[3], $res[1]) + ) { + $this->_addFailure('date', 'invalid release date "' . + $this->_packagexml->getDate() . '"'); + return false; + } + + if ($this->_state == PEAR_VALIDATE_PACKAGING && + $this->_packagexml->getDate() != date('Y-m-d')) { + $this->_addWarning('date', 'Release Date "' . + $this->_packagexml->getDate() . '" is not today'); + } + } + return true; + } + + /** + * @access protected + */ + function validateTime() + { + if (!$this->_packagexml->getTime()) { + // default of no time value set + return true; + } + + // packager automatically sets time, so only validate if pear validate is called + if ($this->_state = PEAR_VALIDATE_NORMAL) { + if (!preg_match('/\d\d:\d\d:\d\d/', + $this->_packagexml->getTime())) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + + $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches); + if ($result === false || empty($matches)) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + } + + return true; + } + + /** + * @access protected + */ + function validateState() + { + // this is the closest to "final" php4 can get + if (!PEAR_Validate::validState($this->_packagexml->getState())) { + if (strtolower($this->_packagexml->getState() == 'rc')) { + $this->_addFailure('state', 'RC is not a state, it is a version ' . + 'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta'); + } + $this->_addFailure('state', 'invalid release state "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + return false; + } + return true; + } + + /** + * @access protected + */ + function validateStability() + { + $ret = true; + $packagestability = $this->_packagexml->getState(); + $apistability = $this->_packagexml->getState('api'); + if (!PEAR_Validate::validState($packagestability)) { + $this->_addFailure('state', 'invalid release stability "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + $ret = false; + } + $apistates = PEAR_Validate::getValidStates(); + array_shift($apistates); // snapshot is not allowed + if (!in_array($apistability, $apistates)) { + $this->_addFailure('state', 'invalid API stability "' . + $this->_packagexml->getState('api') . '", must be one of: ' . + implode(', ', $apistates)); + $ret = false; + } + return $ret; + } + + /** + * @access protected + */ + function validateSummary() + { + return true; + } + + /** + * @access protected + */ + function validateDescription() + { + return true; + } + + /** + * @access protected + */ + function validateLicense() + { + return true; + } + + /** + * @access protected + */ + function validateNotes() + { + return true; + } + + /** + * for package.xml 2.0 only - channels can't use package.xml 1.0 + * @access protected + */ + function validateDependencies() + { + return true; + } + + /** + * for package.xml 1.0 only + * @access private + */ + function _validateFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateMainFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateReleaseFilelist() + { + return true; // placeholder for now + } + + /** + * @access protected + */ + function validateChangelog() + { + return true; + } + + /** + * @access protected + */ + function validateFilelist() + { + return true; + } + + /** + * @access protected + */ + function validateDeps() + { + return true; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR/Validator/PECL.php b/includes/pear/PEAR/Validator/PECL.php new file mode 100644 index 0000000..7e0769a --- /dev/null +++ b/includes/pear/PEAR/Validator/PECL.php @@ -0,0 +1,63 @@ +<?php +/** + * Channel Validator for the pecl.php.net channel + * + * PHP 4 and PHP 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a5 + */ +/** + * This is the parent class for all validators + */ +require_once 'PEAR/Validate.php'; +/** + * Channel Validator for the pecl.php.net channel + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a5 + */ +class PEAR_Validator_PECL extends PEAR_Validate +{ + function validateVersion() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + $last = array_pop($versioncomponents); + if (substr($last, 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions must have ' . + 'upper-case RC, not lower-case rc'); + return false; + } + } + return true; + } + + function validatePackageName() + { + $ret = parent::validatePackageName(); + if ($this->_packagexml->getPackageType() == 'extsrc' || + $this->_packagexml->getPackageType() == 'zendextsrc') { + if (strtolower($this->_packagexml->getPackage()) != + strtolower($this->_packagexml->getProvidesExtension())) { + $this->_addWarning('providesextension', 'package name "' . + $this->_packagexml->getPackage() . '" is different from extension name "' . + $this->_packagexml->getProvidesExtension() . '"'); + } + } + return $ret; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/Warning.php b/includes/pear/PEAR/Warning.php new file mode 100644 index 0000000..17ac905 --- /dev/null +++ b/includes/pear/PEAR/Warning.php @@ -0,0 +1,379 @@ +<?php +// +----------------------------------------------------------------------+ +// | PEAR_Warning | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004 The PEAR Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 3.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/3_0.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Authors: Greg Beaver <cellog@php.net> | +// +----------------------------------------------------------------------+ +// +// $Id$ +require_once 'PEAR/Exception.php'; + +/** + * Exception class for internal PEAR_Warning exceptions + * @package PEAR + */ +class PEAR_WarningException extends PEAR_Exception {} + +interface PEAR_WarningInterface +{ + /** + * Get the severity of this warning ('warning', 'notice') + * @return string + */ + public function getLevel(); +} + +/** + * Warning mechanism for PEAR PHP5-only packages. + * + * For users: + * + * Unlike PEAR_ErrorStack, PEAR_Warning is designed to be used on a transactional basis. + * + * <code> + * <?php + * require_once 'PEAR/Warning.php'; + * require_once 'PEAR/Exception.php'; + * class Mypackage_Exception extends PEAR_Exception {} + * PEAR_Warning::begin(); + * $c = new Somepackage; + * $c->doSomethingComplex(); + * if (PEAR_Warning::hasWarnings()) { + * $warnings = PEAR_Warning::end(); + * throw new Mypackage_Exception('unclean doSomethingComplex', $warnings); + * } else { + * $c->doSomethingElse(); + * } + * ?> + * </code> + * + * Only warnings that occur between ::begin() and ::end() will be processed. Remember, + * a warning is a non-fatal error, exceptions will be used for unrecoverable errors in + * all PEAR packages, and you should follow this model to be safe! + * + * For developers: + * + * This class can be used globally or locally. For global use, a + * series of static methods have been provided. The class is designed + * for lazy loading, and so the following code will work, and increase + * efficiency on production servers: + * + * <code> + * <?php + * if (class_exists('PEAR_Warning')) { + * PEAR_Warning::add(1, 'mypackage', 'possible mis-spelling of something'); + * } + * ?> + * </code> + * + * This means that PEAR_Warning can literally be used without the need for + * require_once 'PEAR/Warning.php';! + * + * You can also pass in an exception class as a warning + * + * <code> + * <?php + * class MyPackage_Warning extends PEAR_Exception {} + * PEAR_Warning::add(new MyPackage_Warning('some info')); + * ?> + * </code> + * + * An interface is provided to allow for severity differentiation + * + * <code> + * <?php + * class MyPackage_Warning extends PEAR_Exception implements PEAR_WarningInterface + * { + * private $_level = 'warning'; + * function __construct($message, $level = 'warning', $p1 = null, $p2 = null) + * { + * $this->_level = $level; + * parent::__construct($message, $p1, $p2); + * } + * + * public function getLevel() + * { + * return $this->_level; + * } + * } + * PEAR_Warning::add(new MyPackage_Warning('some info', 'notice')); + * ?> + * </code> + * + * This can be used with {@link setErrorHandling()} to ignore warnings of different severities + * for complex error situations. + * + * For local situations like an internal warning system for a parser that may become the cause + * of a single PEAR_Exception, PEAR_Warning can also be instantiated and used without any connection + * to the global warning stack. + * @package PEAR + */ +class PEAR_Warning +{ + /** + * properties used for global warning stacks + */ + protected static $_hasWarnings = false; + + protected static $warnings = array(); + protected static $go = false; + protected static $levels = array('warning', 'notice'); + + private static $_observers = array(); + private static $_uniqueid = 0; + /** + * properties used for instantiation of private warning stack + */ + private $_warnings = array(); + private $_go = false; + private $_context; + + /** + * Begin tracking all global warnings + */ + static public function begin() + { + if (class_exists('PEAR_ErrorStack')) { + PEAR_ErrorStack::setPEARWarningCallback(array('PEAR_Warning', '_catch')); + } + self::$go = true; + self::$_hasWarnings = false; + } + + /** + * @return bool + */ + static public function hasWarnings() + { + return self::$_hasWarnings; + } + + /** + * Stop tracking global warnings + * @return array an array of all warnings in array and PEAR_Exception format + * suitable for use as a PEAR_Exception cause + */ + static public function end() + { + if (class_exists('PEAR_ErrorStack')) { + PEAR_ErrorStack::setPEARWarningCallback(false); + } + self::$go = false; + self::$_hasWarnings = false; + $a = self::$warnings; + self::$warnings = array(); + return $a; + } + + /** + * @param mixed A valid callback that accepts either a + * PEAR_Exception or PEAR_ErrorStack-style array + * @param string The name of the observer. Use this if you want + * to remove it later with removeObserver(). + * {@link getUniqueId()} can be used to generate a label + */ + public static function addObserver($callback, $label = 'default') + { + self::$_observers[$label] = $callback; + } + + /** + * @param mixed observer ID + */ + public static function removeObserver($label = 'default') + { + unset(self::$_observers[$label]); + } + + /** + * @return int unique identifier for an observer + */ + public static function getUniqueId() + { + return self::$_uniqueid++; + } + + /** + * Set the warning levels that should be captured by the warning mechanism + * + * WARNING: no error checking or spell checking. + * @param array + */ + public static function setErrorHandling($levels) + { + self::$_levels = $levels; + } + + /** + * Add a warning to the global warning stack. + * + * Note: if you want file/line context, use an exception object + * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an + * error code or some other error class differentiation technique + * @param string Package is required if $codeOrException is not a PEAR_Exception object + * @param string Error message, use %param% to do automatic parameter replacement from $params + * @param array Error parameters + * @param string Error level, use the English name + + * @throws PEAR_WarningException if $codeOrException is not a PEAR_Exception and $package is not set + */ + static public function add($codeOrException, $package = '', $msg = '', $params = array(), + $level = 'warning') + { + if ($codeOrException instanceof PEAR_Exception) { + if ($codeOrException instanceof PEAR_WarningInterface) { + if (in_array($codeOrException->getLevel(), self::$levels)) { + self::_signal($codeOrException); + } + } else { + self::_signal($codeOrException); + } + } else { + if (empty($package)) { + throw new PEAR_WarningException('Package must be set for a non-exception warning'); + } + if (in_array($level, self::$levels)) { + $warning = self::_formatWarning($codeOrException, $package, $level, $msg, $params); + self::_signal($warning); + } + } + if (self::$go) { + self::$_hasWarnings = true; + self::$warnings[] = $warning; + } + } + + /** + * @param string the package name, or other context information that can be used + * to differentiate this warning from warnings thrown by other packages + * @throws PEAR_WarningException if $context is not a string + */ + public function __construct($context) + { + if (!is_string($context)) { + throw new PEAR_WarningException('$context constructor argument must be a string'); + } + $this->_context = $context; + } + + /** + * Local stack function for adding a warning - note that package is not needed, as it is + * defined in the constructor. + * + * Note: if you want file/line context, use an exception object + * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an + * error code or some other error class differentiation technique + * @param string Error message, use %param% to do automatic parameter replacement from $params + * @param array Error parameters + * @param string Error level, use the English name + */ + public function localAdd($code, $msg = '', $params = array(), $level = 'warning') + { + if ($codeOrException instanceof PEAR_Exception) { + $this->_warnings[] = $codeOrException; + } else { + $warning = self::_formatWarning($codeOrException, $this->_context, $level, $msg); + $this->_warnings[] = $warning; + } + } + + /** + * Begin a local warning stack session + */ + public function localBegin() + { + $this->_warnings = array(); + $this->_go = true; + } + + /** + * End a local warning stack session + * @return array + */ + public function localEnd() + { + $a = $this->_warnings; + $this->_warnings = array(); + $this->_go = false; + return $a; + } + + /** + * Do not use this function directly - it should only be used by PEAR_ErrorStack + * @access private + */ + static public function _catch($err) + { + self::_signal($err); + } + + private static function _signal($warning) + { + foreach (self::$_observers as $func) { + if (is_callable($func)) { + call_user_func($func, $this); + continue; + } + settype($func, 'array'); + switch ($func[0]) { + case PEAR_EXCEPTION_PRINT : + $f = (isset($func[1])) ? $func[1] : '%s'; + printf($f, $this->getMessage()); + break; + case PEAR_EXCEPTION_TRIGGER : + $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE; + trigger_error($this->getMessage(), $f); + break; + case PEAR_EXCEPTION_DIE : + $f = (isset($func[1])) ? $func[1] : '%s'; + die(printf($f, $this->getMessage())); + break; + default: + trigger_error('invalid observer type', E_USER_WARNING); + } + } + } + + static private function _formatWarning($code, $package, $level, $msg, $params, $backtrace) + { + return array('package' => $package, + 'code' => $code, + 'level' => $level, + 'message' => self::_formatMessage($msg, $params), + 'params' => $params); + } + + static private function _formatMessage($msg, $params) + { + if (count($params)) { + foreach ($params as $name => $val) { + if (strpos($msg, '%' . $name . '%') !== false) { + if (is_array($val)) { + // don't pass in an array that you expect to display unless it is 1-dimensional! + $val = implode(', ', $val); + } + if (is_object($val)) { + if (method_exists($val, '__toString')) { + $val = $val->__toString(); + } else { + $val = 'Object'; + } + } + $msg = str_replace('%' . $name . '%', $val, $msg); + } + } + } + return $msg; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/PEAR/XMLParser.php b/includes/pear/PEAR/XMLParser.php new file mode 100644 index 0000000..a70988b --- /dev/null +++ b/includes/pear/PEAR/XMLParser.php @@ -0,0 +1,253 @@ +<?php +/** + * PEAR_XMLParser + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Parser for any xml file + * @category pear + * @package PEAR + * @author Greg Beaver <cellog@php.net> + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_XMLParser +{ + /** + * unserilialized data + * @var string $_serializedData + */ + var $_unserializedData = null; + + /** + * name of the root tag + * @var string $_root + */ + var $_root = null; + + /** + * stack for all data that is found + * @var array $_dataStack + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * @var array $_valStack + */ + var $_valStack = array(); + + /** + * current tag depth + * @var int $_depth + */ + var $_depth = 0; + + /** + * The XML encoding to use + * @var string $encoding + */ + var $encoding = 'ISO-8859-1'; + + /** + * @return array + */ + function getData() + { + return $this->_unserializedData; + } + + /** + * @param string xml content + * @return true|PEAR_Error + */ + function parse($data) + { + if (!extension_loaded('xml')) { + include_once 'PEAR.php'; + return PEAR::raiseError("XML Extension not found", 1); + } + $this->_dataStack = $this->_valStack = array(); + $this->_depth = 0; + + if ( + strpos($data, 'encoding="UTF-8"') + || strpos($data, 'encoding="utf-8"') + || strpos($data, "encoding='UTF-8'") + || strpos($data, "encoding='utf-8'") + ) { + $this->encoding = 'UTF-8'; + } + + if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') { + $data = utf8_decode($data); + $this->encoding = 'ISO-8859-1'; + } + + $xp = xml_parser_create($this->encoding); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xp, $this); + xml_set_element_handler($xp, 'startHandler', 'endHandler'); + xml_set_character_data_handler($xp, 'cdataHandler'); + if (!xml_parse($xp, $data)) { + $msg = xml_error_string(xml_get_error_code($xp)); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + include_once 'PEAR.php'; + return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2); + } + xml_parser_free($xp); + return true; + } + + /** + * Start element handler for XML parser + * + * @access private + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * @return void + */ + function startHandler($parser, $element, $attribs) + { + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => 'string', + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if (count($attribs) > 0) { + $val['children'] = array(); + $val['type'] = 'array'; + $val['children']['attribs'] = $attribs; + } + + array_push($this->_valStack, $val); + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + return trim($data); + } + + /** + * End element handler for XML parser + * + * @access private + * @param object XML parser object + * @param string + * @return void + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + $data = $this->postProcess($this->_dataStack[$this->_depth], $element); + + // adjust type of the value + switch (strtolower($value['type'])) { + // unserialize an array + case 'array': + if ($data !== '') { + $value['children']['_content'] = $data; + } + + $value['value'] = isset($value['children']) ? $value['children'] : array(); + break; + + /* + * unserialize a null value + */ + case 'null': + $data = null; + break; + + /* + * unserialize any scalar value + */ + default: + settype($data, $value['type']); + $value['value'] = $data; + break; + } + + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } + + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if ($parent['type'] != 'array') { + $parent['type'] = 'array'; + } + } + + if (!empty($value['name'])) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys'])) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'],$value['value']); + } + array_push($this->_valStack, $parent); + + $this->_depth--; + } + + /** + * Handler for character data + * + * @access private + * @param object XML parser object + * @param string CDATA + * @return void + */ + function cdataHandler($parser, $cdata) + { + $this->_dataStack[$this->_depth] .= $cdata; + } +}
\ No newline at end of file diff --git a/includes/pear/PEAR5.php b/includes/pear/PEAR5.php new file mode 100644 index 0000000..4286067 --- /dev/null +++ b/includes/pear/PEAR5.php @@ -0,0 +1,33 @@ +<?php +/** + * This is only meant for PHP 5 to get rid of certain strict warning + * that doesn't get hidden since it's in the shutdown function + */ +class PEAR5 +{ + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR5::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } +}
\ No newline at end of file diff --git a/includes/pear/Structures/Graph.php b/includes/pear/Structures/Graph.php new file mode 100644 index 0000000..3757f15 --- /dev/null +++ b/includes/pear/Structures/Graph.php @@ -0,0 +1,154 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ +// +-----------------------------------------------------------------------------+ +// | Copyright (c) 2003 Sérgio Gonçalves Carvalho | +// +-----------------------------------------------------------------------------+ +// | This file is part of Structures_Graph. | +// | | +// | Structures_Graph is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU Lesser General Public License as published by | +// | the Free Software Foundation; either version 2.1 of the License, or | +// | (at your option) any later version. | +// | | +// | Structures_Graph 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 Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public License | +// | along with Structures_Graph; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | +// | 02111-1307 USA | +// +-----------------------------------------------------------------------------+ +// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> | +// +-----------------------------------------------------------------------------+ +// +/** + * The Graph.php file contains the definition of the Structures_Graph class + * + * @see Structures_Graph + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** PEAR base classes */ +require_once 'PEAR.php'; +/** Graph Node */ +require_once 'Structures/Graph/Node.php'; +/* }}} */ + +define('STRUCTURES_GRAPH_ERROR_GENERIC', 100); + +/* class Structures_Graph {{{ */ +/** + * The Structures_Graph class represents a graph data structure. + * + * A Graph is a data structure composed by a set of nodes, connected by arcs. + * Graphs may either be directed or undirected. In a directed graph, arcs are + * directional, and can be traveled only one way. In an undirected graph, arcs + * are bidirectional, and can be traveled both ways. + * + * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com> + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +/* }}} */ +class Structures_Graph { + /* fields {{{ */ + /** + * @access private + */ + var $_nodes = array(); + /** + * @access private + */ + var $_directed = false; + /* }}} */ + + /* Constructor {{{ */ + /** + * + * Constructor + * + * @param boolean Set to true if the graph is directed. Set to false if it is not directed. (Optional, defaults to true) + * @access public + */ + function Structures_Graph($directed = true) { + $this->_directed = $directed; + } + /* }}} */ + + /* isDirected {{{ */ + /** + * + * Return true if a graph is directed + * + * @return boolean true if the graph is directed + * @access public + */ + function isDirected() { + return (boolean) $this->_directed; + } + /* }}} */ + + /* addNode {{{ */ + /** + * + * Add a Node to the Graph + * + * @param Structures_Graph_Node The node to be added. + * @access public + */ + function addNode(&$newNode) { + // We only add nodes + if (!is_a($newNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph::addNode received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC); + // Graphs are node *sets*, so duplicates are forbidden. We allow nodes that are exactly equal, but disallow equal references. + foreach($this->_nodes as $key => $node) { + /* + ZE1 equality operators choke on the recursive cycle introduced by the _graph field in the Node object. + So, we'll check references the hard way (change $this->_nodes[$key] and check if the change reflects in + $node) + */ + $savedData = $this->_nodes[$key]; + $referenceIsEqualFlag = false; + $this->_nodes[$key] = true; + if ($node === true) { + $this->_nodes[$key] = false; + if ($node === false) $referenceIsEqualFlag = true; + } + $this->_nodes[$key] = $savedData; + if ($referenceIsEqualFlag) return Pear::raiseError('Structures_Graph::addNode received an object that is a duplicate for this dataset', STRUCTURES_GRAPH_ERROR_GENERIC); + } + $this->_nodes[] =& $newNode; + $newNode->setGraph($this); + } + /* }}} */ + + /* removeNode (unimplemented) {{{ */ + /** + * + * Remove a Node from the Graph + * + * @todo This is unimplemented + * @param Structures_Graph_Node The node to be removed from the graph + * @access public + */ + function removeNode(&$node) { + } + /* }}} */ + + /* getNodes {{{ */ + /** + * + * Return the node set, in no particular order. For ordered node sets, use a Graph Manipulator insted. + * + * @access public + * @see Structures_Graph_Manipulator_TopologicalSorter + * @return array The set of nodes in this graph + */ + function &getNodes() { + return $this->_nodes; + } + /* }}} */ +} +?> diff --git a/includes/pear/Structures/Graph/Manipulator/AcyclicTest.php b/includes/pear/Structures/Graph/Manipulator/AcyclicTest.php new file mode 100644 index 0000000..fc1ba92 --- /dev/null +++ b/includes/pear/Structures/Graph/Manipulator/AcyclicTest.php @@ -0,0 +1,136 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ +// +-----------------------------------------------------------------------------+ +// | Copyright (c) 2003 Sérgio Gonçalves Carvalho | +// +-----------------------------------------------------------------------------+ +// | This file is part of Structures_Graph. | +// | | +// | Structures_Graph is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU Lesser General Public License as published by | +// | the Free Software Foundation; either version 2.1 of the License, or | +// | (at your option) any later version. | +// | | +// | Structures_Graph 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 Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public License | +// | along with Structures_Graph; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | +// | 02111-1307 USA | +// +-----------------------------------------------------------------------------+ +// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Manipulator_AcyclicTest graph manipulator. + * + * @see Structures_Graph_Manipulator_AcyclicTest + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'PEAR.php'; +/** */ +require_once 'Structures/Graph.php'; +/** */ +require_once 'Structures/Graph/Node.php'; +/* }}} */ + +/* class Structures_Graph_Manipulator_AcyclicTest {{{ */ +/** + * The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator + * which tests whether a graph contains a cycle. + * + * The definition of an acyclic graph used in this manipulator is that of a + * DAG. The graph must be directed, or else it is considered cyclic, even when + * there are no arcs. + * + * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com> + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +class Structures_Graph_Manipulator_AcyclicTest { + /* _nonVisitedInDegree {{{ */ + /** + * + * This is a variant of Structures_Graph::inDegree which does + * not count nodes marked as visited. + * + * @access private + * @return integer Number of non-visited nodes that link to this one + */ + function _nonVisitedInDegree(&$node) { + $result = 0; + $graphNodes =& $node->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ((!$graphNodes[$key]->getMetadata('acyclic-test-visited')) && $graphNodes[$key]->connectsTo($node)) $result++; + } + return $result; + + } + /* }}} */ + + /* _isAcyclic {{{ */ + /** + * @access private + */ + function _isAcyclic(&$graph) { + // Mark every node as not visited + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + $refGenerator = array(); + foreach($nodeKeys as $key) { + $refGenerator[] = false; + $nodes[$key]->setMetadata('acyclic-test-visited', $refGenerator[sizeof($refGenerator) - 1]); + } + + // Iteratively peel off leaf nodes + do { + // Find out which nodes are leafs (excluding visited nodes) + $leafNodes = array(); + foreach($nodeKeys as $key) { + if ((!$nodes[$key]->getMetadata('acyclic-test-visited')) && Structures_Graph_Manipulator_AcyclicTest::_nonVisitedInDegree($nodes[$key]) == 0) { + $leafNodes[] =& $nodes[$key]; + } + } + // Mark leafs as visited + for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) { + $visited =& $leafNodes[$i]->getMetadata('acyclic-test-visited'); + $visited = true; + $leafNodes[$i]->setMetadata('acyclic-test-visited', $visited); + } + } while (sizeof($leafNodes) > 0); + + // If graph is a DAG, there should be no non-visited nodes. Let's try to prove otherwise + $result = true; + foreach($nodeKeys as $key) if (!$nodes[$key]->getMetadata('acyclic-test-visited')) $result = false; + + // Cleanup visited marks + foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('acyclic-test-visited'); + + return $result; + } + /* }}} */ + + /* isAcyclic {{{ */ + /** + * + * isAcyclic returns true if a graph contains no cycles, false otherwise. + * + * @return boolean true iff graph is acyclic + * @access public + */ + function isAcyclic(&$graph) { + // We only test graphs + if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_AcyclicTest::isAcyclic received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if (!$graph->isDirected()) return false; // Only directed graphs may be acyclic + + return Structures_Graph_Manipulator_AcyclicTest::_isAcyclic($graph); + } + /* }}} */ +} +/* }}} */ +?> diff --git a/includes/pear/Structures/Graph/Manipulator/TopologicalSorter.php b/includes/pear/Structures/Graph/Manipulator/TopologicalSorter.php new file mode 100644 index 0000000..98a9fa0 --- /dev/null +++ b/includes/pear/Structures/Graph/Manipulator/TopologicalSorter.php @@ -0,0 +1,153 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ +// +-----------------------------------------------------------------------------+ +// | Copyright (c) 2003 Sérgio Gonçalves Carvalho | +// +-----------------------------------------------------------------------------+ +// | This file is part of Structures_Graph. | +// | | +// | Structures_Graph is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU Lesser General Public License as published by | +// | the Free Software Foundation; either version 2.1 of the License, or | +// | (at your option) any later version. | +// | | +// | Structures_Graph 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 Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public License | +// | along with Structures_Graph; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | +// | 02111-1307 USA | +// +-----------------------------------------------------------------------------+ +// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Manipulator_TopologicalSorter class. + * + * @see Structures_Graph_Manipulator_TopologicalSorter + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'PEAR.php'; +/** */ +require_once 'Structures/Graph.php'; +/** */ +require_once 'Structures/Graph/Node.php'; +/** */ +require_once 'Structures/Graph/Manipulator/AcyclicTest.php'; +/* }}} */ + +/* class Structures_Graph_Manipulator_TopologicalSorter {{{ */ +/** + * The Structures_Graph_Manipulator_TopologicalSorter is a manipulator + * which is able to return the set of nodes in a graph, sorted by topological + * order. + * + * A graph may only be sorted topologically iff it's a DAG. You can test it + * with the Structures_Graph_Manipulator_AcyclicTest. + * + * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com> + * @copyright (c) 2004 by Sérgio Carvalho + * @see Structures_Graph_Manipulator_AcyclicTest + * @package Structures_Graph + */ +class Structures_Graph_Manipulator_TopologicalSorter { + /* _nonVisitedInDegree {{{ */ + /** + * + * This is a variant of Structures_Graph::inDegree which does + * not count nodes marked as visited. + * + * @access private + * @return integer Number of non-visited nodes that link to this one + */ + function _nonVisitedInDegree(&$node) { + $result = 0; + $graphNodes =& $node->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ((!$graphNodes[$key]->getMetadata('topological-sort-visited')) && $graphNodes[$key]->connectsTo($node)) $result++; + } + return $result; + + } + /* }}} */ + + /* _sort {{{ */ + /** + * @access private + */ + function _sort(&$graph) { + // Mark every node as not visited + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + $refGenerator = array(); + foreach($nodeKeys as $key) { + $refGenerator[] = false; + $nodes[$key]->setMetadata('topological-sort-visited', $refGenerator[sizeof($refGenerator) - 1]); + } + + // Iteratively peel off leaf nodes + $topologicalLevel = 0; + do { + // Find out which nodes are leafs (excluding visited nodes) + $leafNodes = array(); + foreach($nodeKeys as $key) { + if ((!$nodes[$key]->getMetadata('topological-sort-visited')) && Structures_Graph_Manipulator_TopologicalSorter::_nonVisitedInDegree($nodes[$key]) == 0) { + $leafNodes[] =& $nodes[$key]; + } + } + // Mark leafs as visited + $refGenerator[] = $topologicalLevel; + for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) { + $visited =& $leafNodes[$i]->getMetadata('topological-sort-visited'); + $visited = true; + $leafNodes[$i]->setMetadata('topological-sort-visited', $visited); + $leafNodes[$i]->setMetadata('topological-sort-level', $refGenerator[sizeof($refGenerator) - 1]); + } + $topologicalLevel++; + } while (sizeof($leafNodes) > 0); + + // Cleanup visited marks + foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('topological-sort-visited'); + } + /* }}} */ + + /* sort {{{ */ + /** + * + * sort returns the graph's nodes, sorted by topological order. + * + * The result is an array with + * as many entries as topological levels. Each entry in this array is an array of nodes within + * the given topological level. + * + * @return array The graph's nodes, sorted by topological order. + * @access public + */ + function sort(&$graph) { + // We only sort graphs + if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if (!Structures_Graph_Manipulator_AcyclicTest::isAcyclic($graph)) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an graph that has cycles', STRUCTURES_GRAPH_ERROR_GENERIC); + + Structures_Graph_Manipulator_TopologicalSorter::_sort($graph); + $result = array(); + + // Fill out result array + $nodes =& $graph->getNodes(); + $nodeKeys = array_keys($nodes); + foreach($nodeKeys as $key) { + if (!array_key_exists($nodes[$key]->getMetadata('topological-sort-level'), $result)) $result[$nodes[$key]->getMetadata('topological-sort-level')] = array(); + $result[$nodes[$key]->getMetadata('topological-sort-level')][] =& $nodes[$key]; + $nodes[$key]->unsetMetadata('topological-sort-level'); + } + + return $result; + } + /* }}} */ +} +/* }}} */ +?> diff --git a/includes/pear/Structures/Graph/Node.php b/includes/pear/Structures/Graph/Node.php new file mode 100644 index 0000000..95afa2b --- /dev/null +++ b/includes/pear/Structures/Graph/Node.php @@ -0,0 +1,342 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ +// +-----------------------------------------------------------------------------+ +// | Copyright (c) 2003 Sérgio Gonçalves Carvalho | +// +-----------------------------------------------------------------------------+ +// | This file is part of Structures_Graph. | +// | | +// | Structures_Graph is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU Lesser General Public License as published by | +// | the Free Software Foundation; either version 2.1 of the License, or | +// | (at your option) any later version. | +// | | +// | Structures_Graph 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 Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public License | +// | along with Structures_Graph; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | +// | 02111-1307 USA | +// +-----------------------------------------------------------------------------+ +// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> | +// +-----------------------------------------------------------------------------+ +// +/** + * This file contains the definition of the Structures_Graph_Node class + * + * @see Structures_Graph_Node + * @package Structures_Graph + */ + +/* dependencies {{{ */ +/** */ +require_once 'PEAR.php'; +/** */ +require_once 'Structures/Graph.php'; +/* }}} */ + +/* class Structures_Graph_Node {{{ */ +/** + * The Structures_Graph_Node class represents a Node that can be member of a + * graph node set. + * + * A graph node can contain data. Under this API, the node contains default data, + * and key index data. It behaves, thus, both as a regular data node, and as a + * dictionary (or associative array) node. + * + * Regular data is accessed via getData and setData. Key indexed data is accessed + * via getMetadata and setMetadata. + * + * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com> + * @copyright (c) 2004 by Sérgio Carvalho + * @package Structures_Graph + */ +/* }}} */ +class Structures_Graph_Node { + /* fields {{{ */ + /** + * @access private + */ + var $_data = null; + /** @access private */ + var $_metadata = array(); + /** @access private */ + var $_arcs = array(); + /** @access private */ + var $_graph = null; + /* }}} */ + + /* Constructor {{{ */ + /** + * + * Constructor + * + * @access public + */ + function Structures_Graph_Node() { + } + /* }}} */ + + /* getGraph {{{ */ + /** + * + * Node graph getter + * + * @return Structures_Graph Graph where node is stored + * @access public + */ + function &getGraph() { + return $this->_graph; + } + /* }}} */ + + /* setGraph {{{ */ + /** + * + * Node graph setter. This method should not be called directly. Use Graph::addNode instead. + * + * @param Structures_Graph Set the graph for this node. + * @see Structures_Graph::addNode() + * @access public + */ + function setGraph(&$graph) { + $this->_graph =& $graph; + } + /* }}} */ + + /* getData {{{ */ + /** + * + * Node data getter. + * + * Each graph node can contain a reference to one variable. This is the getter for that reference. + * + * @return mixed Data stored in node + * @access public + */ + function &getData() { + return $this->_data; + } + /* }}} */ + + /* setData {{{ */ + /** + * + * Node data setter + * + * Each graph node can contain a reference to one variable. This is the setter for that reference. + * + * @return mixed Data to store in node + * @access public + */ + function setData($data) { + $this->_data =& $data; + } + /* }}} */ + + /* metadataKeyExists {{{ */ + /** + * + * Test for existence of metadata under a given key. + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method tests whether a given metadata key exists for this node. + * + * @param string Key to test + * @return boolean + * @access public + */ + function metadataKeyExists($key) { + return array_key_exists($key, $this->_metadata); + } + /* }}} */ + + /* getMetadata {{{ */ + /** + * + * Node metadata getter + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method gets the data under the given key. If the key does + * not exist, an error will be thrown, so testing using metadataKeyExists might be needed. + * + * @param string Key + * @param boolean nullIfNonexistent (defaults to false). + * @return mixed Metadata Data stored in node under given key + * @see metadataKeyExists + * @access public + */ + function &getMetadata($key, $nullIfNonexistent = false) { + if (array_key_exists($key, $this->_metadata)) { + return $this->_metadata[$key]; + } else { + if ($nullIfNonexistent) { + $a = null; + return $a; + } else { + $a = Pear::raiseError('Structures_Graph_Node::getMetadata: Requested key does not exist', STRUCTURES_GRAPH_ERROR_GENERIC); + return $a; + } + } + } + /* }}} */ + + /* unsetMetadata {{{ */ + /** + * + * Delete metadata by key + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method removes any data that might be stored under the provided key. + * If the key does not exist, no error is thrown, so it is safe using this method without testing for key existence. + * + * @param string Key + * @access public + */ + function unsetMetadata($key) { + if (array_key_exists($key, $this->_metadata)) unset($this->_metadata[$key]); + } + /* }}} */ + + /* setMetadata {{{ */ + /** + * + * Node metadata setter + * + * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an + * associative array or in a dictionary. This method stores data under the given key. If the key already exists, + * previously stored data is discarded. + * + * @param string Key + * @param mixed Data + * @access public + */ + function setMetadata($key, $data) { + $this->_metadata[$key] =& $data; + } + /* }}} */ + + /* _connectTo {{{ */ + /** @access private */ + function _connectTo(&$destinationNode) { + $this->_arcs[] =& $destinationNode; + } + /* }}} */ + + /* connectTo {{{ */ + /** + * + * Connect this node to another one. + * + * If the graph is not directed, the reverse arc, connecting $destinationNode to $this is also created. + * + * @param Structures_Graph_Node Node to connect to + * @access public + */ + function connectTo(&$destinationNode) { + // We only connect to nodes + if (!is_a($destinationNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph_Node::connectTo received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC); + // Nodes must already be in graphs to be connected + if ($this->_graph == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC); + if ($destinationNode->getGraph() == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect to a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC); + // Connect here + $this->_connectTo($destinationNode); + // If graph is undirected, connect back + if (!$this->_graph->isDirected()) { + $destinationNode->_connectTo($this); + } + } + /* }}} */ + + /* getNeighbours {{{ */ + /** + * + * Return nodes connected to this one. + * + * @return array Array of nodes + * @access public + */ + function getNeighbours() { + return $this->_arcs; + } + /* }}} */ + + /* connectsTo {{{ */ + /** + * + * Test wether this node has an arc to the target node + * + * @return boolean True if the two nodes are connected + * @access public + */ + function connectsTo(&$target) { + if (version_compare(PHP_VERSION, '5.0.0') >= 0) { + return in_array($target, $this->getNeighbours(), true); + } + + $copy = $target; + $arcKeys = array_keys($this->_arcs); + foreach($arcKeys as $key) { + /* ZE1 chokes on this expression: + if ($target === $arc) return true; + so, we'll use more convoluted stuff + */ + $arc =& $this->_arcs[$key]; + $target = true; + if ($arc === true) { + $target = false; + if ($arc === false) { + $target = $copy; + return true; + } + } + } + $target = $copy; + return false; + } + /* }}} */ + + /* inDegree {{{ */ + /** + * + * Calculate the in degree of the node. + * + * The indegree for a node is the number of arcs entering the node. For non directed graphs, + * the indegree is equal to the outdegree. + * + * @return integer In degree of the node + * @access public + */ + function inDegree() { + if ($this->_graph == null) return 0; + if (!$this->_graph->isDirected()) return $this->outDegree(); + $result = 0; + $graphNodes =& $this->_graph->getNodes(); + foreach (array_keys($graphNodes) as $key) { + if ($graphNodes[$key]->connectsTo($this)) $result++; + } + return $result; + + } + /* }}} */ + + /* outDegree {{{ */ + /** + * + * Calculate the out degree of the node. + * + * The outdegree for a node is the number of arcs exiting the node. For non directed graphs, + * the outdegree is always equal to the indegree. + * + * @return integer Out degree of the node + * @access public + */ + function outDegree() { + if ($this->_graph == null) return 0; + return sizeof($this->_arcs); + } + /* }}} */ +} +?> diff --git a/includes/pear/System.php b/includes/pear/System.php new file mode 100644 index 0000000..b419605 --- /dev/null +++ b/includes/pear/System.php @@ -0,0 +1,624 @@ +<?php +/** + * File/Directory manipulation + * + * PHP versions 4 and 5 + * + * @category pear + * @package System + * @author Tomas V.V.Cox <cox@idecnet.com> + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR.php'; +require_once 'Console/Getopt.php'; + +$GLOBALS['_System_temp_files'] = array(); + +/** +* System offers cross plattform compatible system functions +* +* Static functions for different operations. Should work under +* Unix and Windows. The names and usage has been taken from its respectively +* GNU commands. The functions will return (bool) false on error and will +* trigger the error with the PHP trigger_error() function (you can silence +* the error by prefixing a '@' sign after the function call, but this +* is not recommended practice. Instead use an error handler with +* {@link set_error_handler()}). +* +* Documentation on this class you can find in: +* http://pear.php.net/manual/ +* +* Example usage: +* if (!@System::rm('-r file1 dir1')) { +* print "could not delete file1 or dir1"; +* } +* +* In case you need to to pass file names with spaces, +* pass the params as an array: +* +* System::rm(array('-r', $file1, $dir1)); +* +* @category pear +* @package System +* @author Tomas V.V. Cox <cox@idecnet.com> +* @copyright 1997-2006 The PHP Group +* @license http://opensource.org/licenses/bsd-license.php New BSD License +* @version Release: @package_version@ +* @link http://pear.php.net/package/PEAR +* @since Class available since Release 0.1 +* @static +*/ +class System +{ + /** + * returns the commandline arguments of a function + * + * @param string $argv the commandline + * @param string $short_options the allowed option short-tags + * @param string $long_options the allowed option long-tags + * @return array the given options and there values + */ + public static function _parseArgs($argv, $short_options, $long_options = null) + { + if (!is_array($argv) && $argv !== null) { + /* + // Quote all items that are a short option + $av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?<!\\\\)((,\s*)|((?<!,)\s+))?)/i', $argv, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); + $offset = 0; + foreach ($av as $a) { + $b = trim($a[0]); + if ($b{0} == '"' || $b{0} == "'") { + continue; + } + + $escape = escapeshellarg($b); + $pos = $a[1] + $offset; + $argv = substr_replace($argv, $escape, $pos, strlen($b)); + $offset += 2; + } + */ + + // Find all items, quoted or otherwise + preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av); + $argv = $av[1]; + foreach ($av[2] as $k => $a) { + if (empty($a)) { + continue; + } + $argv[$k] = trim($a) ; + } + } + return Console_Getopt::getopt2($argv, $short_options, $long_options); + } + + /** + * Output errors with PHP trigger_error(). You can silence the errors + * with prefixing a "@" sign to the function call: @System::mkdir(..); + * + * @param mixed $error a PEAR error or a string with the error message + * @return bool false + */ + public static function raiseError($error) + { + if (PEAR::isError($error)) { + $error = $error->getMessage(); + } + trigger_error($error, E_USER_WARNING); + return false; + } + + /** + * Creates a nested array representing the structure of a directory + * + * System::_dirToStruct('dir1', 0) => + * Array + * ( + * [dirs] => Array + * ( + * [0] => dir1 + * ) + * + * [files] => Array + * ( + * [0] => dir1/file2 + * [1] => dir1/file3 + * ) + * ) + * @param string $sPath Name of the directory + * @param integer $maxinst max. deep of the lookup + * @param integer $aktinst starting deep of the lookup + * @param bool $silent if true, do not emit errors. + * @return array the structure of the dir + */ + public static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) + { + $struct = array('dirs' => array(), 'files' => array()); + if (($dir = @opendir($sPath)) === false) { + if (!$silent) { + System::raiseError("Could not open dir $sPath"); + } + return $struct; // XXX could not open error + } + + $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ? + $list = array(); + while (false !== ($file = readdir($dir))) { + if ($file != '.' && $file != '..') { + $list[] = $file; + } + } + + closedir($dir); + natsort($list); + if ($aktinst < $maxinst || $maxinst == 0) { + foreach ($list as $val) { + $path = $sPath . DIRECTORY_SEPARATOR . $val; + if (is_dir($path) && !is_link($path)) { + $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent); + $struct = array_merge_recursive($struct, $tmp); + } else { + $struct['files'][] = $path; + } + } + } + + return $struct; + } + + /** + * Creates a nested array representing the structure of a directory and files + * + * @param array $files Array listing files and dirs + * @return array + * @see System::_dirToStruct() + */ + public static function _multipleToStruct($files) + { + $struct = array('dirs' => array(), 'files' => array()); + settype($files, 'array'); + foreach ($files as $file) { + if (is_dir($file) && !is_link($file)) { + $tmp = System::_dirToStruct($file, 0); + $struct = array_merge_recursive($tmp, $struct); + } else { + if (!in_array($file, $struct['files'])) { + $struct['files'][] = $file; + } + } + } + return $struct; + } + + /** + * The rm command for removing files. + * Supports multiple files and dirs and also recursive deletes + * + * @param string $args the arguments for rm + * @return mixed PEAR_Error or true for success + */ + public static function rm($args) + { + $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-) + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + foreach ($opts[0] as $opt) { + if ($opt[0] == 'r') { + $do_recursive = true; + } + } + $ret = true; + if (isset($do_recursive)) { + $struct = System::_multipleToStruct($opts[1]); + foreach ($struct['files'] as $file) { + if (!@unlink($file)) { + $ret = false; + } + } + + rsort($struct['dirs']); + foreach ($struct['dirs'] as $dir) { + if (!@rmdir($dir)) { + $ret = false; + } + } + } else { + foreach ($opts[1] as $file) { + $delete = (is_dir($file)) ? 'rmdir' : 'unlink'; + if (!@$delete($file)) { + $ret = false; + } + } + } + return $ret; + } + + /** + * Make directories. + * + * The -p option will create parent directories + * @param string $args the name of the director(y|ies) to create + * @return bool True for success + */ + public static function mkDir($args) + { + $opts = System::_parseArgs($args, 'pm:'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + $mode = 0777; // default mode + foreach ($opts[0] as $opt) { + if ($opt[0] == 'p') { + $create_parents = true; + } elseif ($opt[0] == 'm') { + // if the mode is clearly an octal number (starts with 0) + // convert it to decimal + if (strlen($opt[1]) && $opt[1]{0} == '0') { + $opt[1] = octdec($opt[1]); + } else { + // convert to int + $opt[1] += 0; + } + $mode = $opt[1]; + } + } + + $ret = true; + if (isset($create_parents)) { + foreach ($opts[1] as $dir) { + $dirstack = array(); + while ((!file_exists($dir) || !is_dir($dir)) && + $dir != DIRECTORY_SEPARATOR) { + array_unshift($dirstack, $dir); + $dir = dirname($dir); + } + + while ($newdir = array_shift($dirstack)) { + if (!is_writeable(dirname($newdir))) { + $ret = false; + break; + } + + if (!mkdir($newdir, $mode)) { + $ret = false; + } + } + } + } else { + foreach($opts[1] as $dir) { + if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) { + $ret = false; + } + } + } + + return $ret; + } + + /** + * Concatenate files + * + * Usage: + * 1) $var = System::cat('sample.txt test.txt'); + * 2) System::cat('sample.txt test.txt > final.txt'); + * 3) System::cat('sample.txt test.txt >> final.txt'); + * + * Note: as the class use fopen, urls should work also (test that) + * + * @param string $args the arguments + * @return boolean true on success + */ + public static function &cat($args) + { + $ret = null; + $files = array(); + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + + $count_args = count($args); + for ($i = 0; $i < $count_args; $i++) { + if ($args[$i] == '>') { + $mode = 'wb'; + $outputfile = $args[$i+1]; + break; + } elseif ($args[$i] == '>>') { + $mode = 'ab+'; + $outputfile = $args[$i+1]; + break; + } else { + $files[] = $args[$i]; + } + } + $outputfd = false; + if (isset($mode)) { + if (!$outputfd = fopen($outputfile, $mode)) { + $err = System::raiseError("Could not open $outputfile"); + return $err; + } + $ret = true; + } + foreach ($files as $file) { + if (!$fd = fopen($file, 'r')) { + System::raiseError("Could not open $file"); + continue; + } + while ($cont = fread($fd, 2048)) { + if (is_resource($outputfd)) { + fwrite($outputfd, $cont); + } else { + $ret .= $cont; + } + } + fclose($fd); + } + if (is_resource($outputfd)) { + fclose($outputfd); + } + return $ret; + } + + /** + * Creates temporary files or directories. This function will remove + * the created files when the scripts finish its execution. + * + * Usage: + * 1) $tempfile = System::mktemp("prefix"); + * 2) $tempdir = System::mktemp("-d prefix"); + * 3) $tempfile = System::mktemp(); + * 4) $tempfile = System::mktemp("-t /var/tmp prefix"); + * + * prefix -> The string that will be prepended to the temp name + * (defaults to "tmp"). + * -d -> A temporary dir will be created instead of a file. + * -t -> The target dir where the temporary (file|dir) will be created. If + * this param is missing by default the env vars TMP on Windows or + * TMPDIR in Unix will be used. If these vars are also missing + * c:\windows\temp or /tmp will be used. + * + * @param string $args The arguments + * @return mixed the full path of the created (file|dir) or false + * @see System::tmpdir() + */ + public static function mktemp($args = null) + { + static $first_time = true; + $opts = System::_parseArgs($args, 't:d'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + foreach ($opts[0] as $opt) { + if ($opt[0] == 'd') { + $tmp_is_dir = true; + } elseif ($opt[0] == 't') { + $tmpdir = $opt[1]; + } + } + + $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp'; + if (!isset($tmpdir)) { + $tmpdir = System::tmpdir(); + } + + if (!System::mkDir(array('-p', $tmpdir))) { + return false; + } + + $tmp = tempnam($tmpdir, $prefix); + if (isset($tmp_is_dir)) { + unlink($tmp); // be careful possible race condition here + if (!mkdir($tmp, 0700)) { + return System::raiseError("Unable to create temporary directory $tmpdir"); + } + } + + $GLOBALS['_System_temp_files'][] = $tmp; + if (isset($tmp_is_dir)) { + //$GLOBALS['_System_temp_files'][] = dirname($tmp); + } + + if ($first_time) { + PEAR::registerShutdownFunc(array('System', '_removeTmpFiles')); + $first_time = false; + } + + return $tmp; + } + + /** + * Remove temporary files created my mkTemp. This function is executed + * at script shutdown time + */ + public static function _removeTmpFiles() + { + if (count($GLOBALS['_System_temp_files'])) { + $delete = $GLOBALS['_System_temp_files']; + array_unshift($delete, '-r'); + System::rm($delete); + $GLOBALS['_System_temp_files'] = array(); + } + } + + /** + * Get the path of the temporal directory set in the system + * by looking in its environments variables. + * Note: php.ini-recommended removes the "E" from the variables_order setting, + * making unavaible the $_ENV array, that s why we do tests with _ENV + * + * @return string The temporary directory on the system + */ + public static function tmpdir() + { + if (OS_WINDOWS) { + if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { + return $var; + } + if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) { + return $var; + } + if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) { + return $var; + } + if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) { + return $var; + } + return getenv('SystemRoot') . '\temp'; + } + if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) { + return $var; + } + return realpath('/tmp'); + } + + /** + * The "which" command (show the full path of a command) + * + * @param string $program The command to search for + * @param mixed $fallback Value to return if $program is not found + * + * @return mixed A string with the full path or false if not found + * @author Stig Bakken <ssb@php.net> + */ + public static function which($program, $fallback = false) + { + // enforce API + if (!is_string($program) || '' == $program) { + return $fallback; + } + + // full path given + if (basename($program) != $program) { + $path_elements[] = dirname($program); + $program = basename($program); + } else { + // Honor safe mode + if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) { + $path = getenv('PATH'); + if (!$path) { + $path = getenv('Path'); // some OSes are just stupid enough to do this + } + } + $path_elements = explode(PATH_SEPARATOR, $path); + } + + if (OS_WINDOWS) { + $exe_suffixes = getenv('PATHEXT') + ? explode(PATH_SEPARATOR, getenv('PATHEXT')) + : array('.exe','.bat','.cmd','.com'); + // allow passing a command.exe param + if (strpos($program, '.') !== false) { + array_unshift($exe_suffixes, ''); + } + // is_executable() is not available on windows for PHP4 + $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file'; + } else { + $exe_suffixes = array(''); + $pear_is_executable = 'is_executable'; + } + + foreach ($exe_suffixes as $suff) { + foreach ($path_elements as $dir) { + $file = $dir . DIRECTORY_SEPARATOR . $program . $suff; + if (@$pear_is_executable($file)) { + return $file; + } + } + } + return $fallback; + } + + /** + * The "find" command + * + * Usage: + * + * System::find($dir); + * System::find("$dir -type d"); + * System::find("$dir -type f"); + * System::find("$dir -name *.php"); + * System::find("$dir -name *.php -name *.htm*"); + * System::find("$dir -maxdepth 1"); + * + * Params implmented: + * $dir -> Start the search at this directory + * -type d -> return only directories + * -type f -> return only files + * -maxdepth <n> -> max depth of recursion + * -name <pattern> -> search pattern (bash style). Multiple -name param allowed + * + * @param mixed Either array or string with the command line + * @return array Array of found files + */ + public static function find($args) + { + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + $dir = realpath(array_shift($args)); + if (!$dir) { + return array(); + } + $patterns = array(); + $depth = 0; + $do_files = $do_dirs = true; + $args_count = count($args); + for ($i = 0; $i < $args_count; $i++) { + switch ($args[$i]) { + case '-type': + if (in_array($args[$i+1], array('d', 'f'))) { + if ($args[$i+1] == 'd') { + $do_files = false; + } else { + $do_dirs = false; + } + } + $i++; + break; + case '-name': + $name = preg_quote($args[$i+1], '#'); + // our magic characters ? and * have just been escaped, + // so now we change the escaped versions to PCRE operators + $name = strtr($name, array('\?' => '.', '\*' => '.*')); + $patterns[] = '('.$name.')'; + $i++; + break; + case '-maxdepth': + $depth = $args[$i+1]; + break; + } + } + $path = System::_dirToStruct($dir, $depth, 0, true); + if ($do_files && $do_dirs) { + $files = array_merge($path['files'], $path['dirs']); + } elseif ($do_dirs) { + $files = $path['dirs']; + } else { + $files = $path['files']; + } + if (count($patterns)) { + $dsq = preg_quote(DIRECTORY_SEPARATOR, '#'); + $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#'; + $ret = array(); + $files_count = count($files); + for ($i = 0; $i < $files_count; $i++) { + // only search in the part of the file below the current directory + $filepart = basename($files[$i]); + if (preg_match($pattern, $filepart)) { + $ret[] = $files[$i]; + } + } + return $ret; + } + return $files; + } +} diff --git a/includes/pear/System/Command.php b/includes/pear/System/Command.php new file mode 100644 index 0000000..f32e8a1 --- /dev/null +++ b/includes/pear/System/Command.php @@ -0,0 +1,598 @@ +<?php +// {{{ license + +// +----------------------------------------------------------------------+ +// | PHP Version 4.0 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2003 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 2.02 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/2_02.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Author: Anders Johannsen <anders@johannsen.com> | +// | Author: Dan Allen <dan@mojavelinux.com> +// +----------------------------------------------------------------------+ + +// $Id$ + +// }}} +// {{{ includes + +require_once 'PEAR.php'; +require_once 'System.php'; + +// }}} +// {{{ constants + +define('SYSTEM_COMMAND_OK', 1); +define('SYSTEM_COMMAND_ERROR', -1); +define('SYSTEM_COMMAND_NO_SHELL', -2); +define('SYSTEM_COMMAND_INVALID_SHELL', -3); +define('SYSTEM_COMMAND_TMPDIR_ERROR', -4); +define('SYSTEM_COMMAND_INVALID_OPERATOR', -5); +define('SYSTEM_COMMAND_INVALID_COMMAND', -6); +define('SYSTEM_COMMAND_OPERATOR_PLACEMENT',-7); +define('SYSTEM_COMMAND_COMMAND_PLACEMENT', -8); +define('SYSTEM_COMMAND_NOHUP_MISSING', -9); +define('SYSTEM_COMMAND_NO_OUTPUT', -10); +define('SYSTEM_COMMAND_STDERR', -11); +define('SYSTEM_COMMAND_NONZERO_EXIT', -12); + +// }}} + +// {{{ class System_Command + +/** + * The System_Command:: class implements an abstraction for various ways + * of executing commands (directly using the backtick operator, + * as a background task after the script has terminated using + * register_shutdown_function() or as a detached process using nohup). + * + * @author Anders Johannsen <anders@johannsen.com> + * @author Dan Allen <dan@mojavelinux.com> + * @version $Revision$ + */ + +// }}} +class System_Command { + // {{{ properties + + /** + * Array of settings used when creating the shell command + * + * @var array + * @access private + */ + var $options = array(); + + /** + * Array of available shells to use to execute the command + * + * @var array + * @access private + */ + var $shells = array(); + + /** + * Array of available control operators used between commands + * + * @var array + * @access private + */ + var $controlOperators = array(); + + /** + * The system command to be executed + * + * @var string + * @access private + */ + var $systemCommand = null; + + /** + * Previously added part to the command string + * + * @var string + * @access private + */ + var $previousElement = null; + + /** + * Directory for writing stderr output + * + * @var string + * @access private + */ + var $tmpDir = null; + + /** + * To allow the pear error object to accumulate when building + * the command, we use the command status to keep track when + * a pear error is raised + * + * @var int + * @access private + */ + var $commandStatus = 0; + + /** + * Hold initialization PEAR_Error + * + * @var object + * @access private + **/ + var $_initError = null; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * Defines all necessary constants and sets defaults + * + * @access public + */ + function System_Command($in_shell = null) + { + // Defining constants + $this->options = array( + 'SEQUENCE' => true, + 'SHUTDOWN' => false, + 'SHELL' => $this->which($in_shell), + 'OUTPUT' => true, + 'NOHUP' => false, + 'BACKGROUND' => false, + 'STDERR' => false, + 'AUTORESET' => false + ); + + // prepare the available control operators + $this->controlOperators = array( + 'PIPE' => '|', + 'AND' => '&&', + 'OR' => '||', + 'GROUP' => ';', + 'LFIFO' => '<', + 'RFIFO' => '>', + ); + + // List of allowed/available shells + $this->shells = array( + 'sh', + 'bash', + 'zsh', + 'tcsh', + 'csh', + 'ash', + 'sash', + 'esh', + 'ksh' + ); + + // Find the first available shell + if (empty($this->options['SHELL'])) { + foreach ($this->shells as $shell) { + if ($this->options['SHELL'] = $this->which($shell)) { + break; + } + } + + // see if we still have no shell + if (empty($this->options['SHELL'])) { + $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_WARNING, null, 'System_Command_Error', true); + return; + } + } + + // Caputre a temporary directory for capturing stderr from commands + $this->tmpDir = System::tmpdir(); + if (!System::mkDir("-p {$this->tmpDir}")) { + $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_TMPDIR_ERROR, null, E_USER_WARNING, null, 'System_Command_Error', true); + return; + } + } + + // }}} + // {{{ setOption() + + /** + * Sets the value for an option. Each option should be set to true + * or false; except the 'SHELL' option which should be a string + * naming a shell. The options are: + * + * 'SEQUENCE' Allow a sequence command or not (right now this is always on); + * + * 'SHUTDOWN' Execute commands via a shutdown function; + * + * 'SHELL' Path to shell; + * + * 'OUTPUT' Output stdout from process; + * + * 'NOHUP' Use nohup to detach process; + * + * 'BACKGROUND' Run as a background process with &; + * + * 'STDERR' Output on stderr will raise an error, even if + * the command's exit value is zero. The output from + * stderr can be retrieved using the getDebugInfo() + * method of the Pear_ERROR object returned by + * execute().; + * + * 'AUTORESET' Automatically call reset() after a successful + * call of execute(); + * + * @param string $in_option is a case-sensitive string, + * corresponding to the option + * that should be changed + * @param mixed $in_setting is the new value for the option + * @access public + * @return bool true if succes, else false + */ + function setOption($in_option, $in_setting) + { + if ($this->_initError) { + return $this->_initError; + } + + $option = strtoupper($in_option); + + if (!isset($this->options[$option])) { + PEAR::raiseError(null, SYSTEM_COMMAND_ERROR, null, E_USER_NOTICE, null, 'System_Command_Error', true); + return false; + } + + switch ($option) { + case 'OUTPUT': + case 'SHUTDOWN': + case 'SEQUENCE': + case 'BACKGROUND': + case 'STDERR': + $this->options[$option] = !empty($in_setting); + return true; + break; + + case 'SHELL': + if (($shell = $this->which($in_setting)) !== false) { + $this->options[$option] = $shell; + return true; + } + else { + PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_NOTICE, $in_setting, 'System_Command_Error', true); + return false; + } + break; + + case 'NOHUP': + if (empty($in_setting)) { + $this->options[$option] = false; + } + else if ($location = $this->which('nohup')) { + $this->options[$option] = $location; + } + else { + PEAR::raiseError(null, SYSTEM_COMMAND_NOHUP_MISSING, null, E_USER_NOTICE, null, 'System_Command_Error', true); + return false; + } + break; + } + } + + // }}} + // {{{ pushCommand() + + /** + * Used to push a command onto the running command to be executed + * + * @param string $in_command binary to be run + * @param string $in_argument either an option or argument value, to be handled appropriately + * @param string $in_argument + * @param ... + * + * @access public + * @return boolean true on success {or System_Command_Error Exception} + */ + function pushCommand($in_command) + { + if ($this->_initError) { + return $this->_initError; + } + + if (!is_null($this->previousElement) && !in_array($this->previousElement, $this->controlOperators)) { + $this->commandStatus = -1; + $error = PEAR::raiseError(null, SYSTEM_COMMAND_COMMAND_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true); + } + + // check for error here + $command = escapeshellcmd($this->which($in_command)); + if ($command === false) { + $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, null, 'System_Command_Error', true); + } + + $argv = func_get_args(); + array_shift($argv); + foreach($argv as $arg) { + if (strpos($arg, '-') === 0) { + $command .= ' ' . $arg; + } + elseif ($arg != '') { + $command .= ' ' . escapeshellarg($arg); + } + } + + $this->previousElement = $command; + $this->systemCommand .= $command; + + return isset($error) ? $error : true; + } + + // }}} + // {{{ pushOperator() + + /** + * Used to push an operator onto the running command to be executed + * + * @param string $in_operator Either string reprentation of operator or system character + * + * @access public + * @return boolean true on success {or System_Command_Error Exception} + */ + function pushOperator($in_operator) + { + if ($this->_initError) { + return $this->_initError; + } + + $operator = isset($this->controlOperators[$in_operator]) ? $this->controlOperators[$in_operator] : $in_operator; + + if (is_null($this->previousElement) || in_array($this->previousElement, $this->controlOperators)) { + $this->commandStatus = -1; + $error = PEAR::raiseError(null, SYSTEM_COMMAND_OPERATOR_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true); + } + elseif (!in_array($operator, $this->controlOperators)) { + $this->commandStatus = -1; + $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_OPERATOR, null, E_USER_WARNING, $operator, 'System_Command_Error', true); + } + + $this->previousElement = $operator; + $this->systemCommand .= ' ' . $operator . ' '; + return isset($error) ? $error : true; + } + + // }}} + // {{{ execute() + + /** + * Executes the code according to given options + * + * @return bool true if success {or System_Command_Exception} + * + * @access public + */ + function execute() + { + if ($this->_initError) { + return $this->_initError; + } + + // if the command is empty or if the last element was a control operator, we can't continue + if (is_null($this->previousElement) || $this->commandStatus == -1 || in_array($this->previousElement, $this->controlOperators)) { + return PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, $this->systemCommand, 'System_Command_Error', true); + } + + // Warning about impossible mix of options + if (!empty($this->options['OUTPUT'])) { + if (!empty($this->options['SHUTDOWN']) || !empty($this->options['NOHUP'])) { + return PEAR::raiseError(null, SYSTEM_COMMAND_NO_OUTPUT, null, E_USER_WARNING, null, 'System_Command_Error', true); + } + } + + // if this is not going to stdout, then redirect to /dev/null + if (empty($this->options['OUTPUT'])) { + $this->systemCommand .= ' >/dev/null'; + } + + $suffix = ''; + // run a command immune to hangups, with output to a non-tty + if (!empty($this->options['NOHUP'])) { + $this->systemCommand = $this->options['NOHUP'] . $this->systemCommand; + } + // run a background process (only if not nohup) + elseif (!empty($this->options['BACKGROUND'])) { + $suffix = ' &'; + } + + // Register to be run on shutdown + if (!empty($this->options['SHUTDOWN'])) { + $line = "system(\"{$this->systemCommand}$suffix\");"; + $function = create_function('', $line); + register_shutdown_function($function); + if ($this->options['AUTORESET']) { + $this->reset(); + } + return true; + } + else { + // send stderr to a file so that we can reap the error message + $tmpFile = tempnam($this->tmpDir, 'System_Command-'); + $this->systemCommand .= ' 2>' . $tmpFile . $suffix; + $shellPipe = $this->which('echo') . ' ' . escapeshellarg($this->systemCommand) . ' | ' . $this->options['SHELL']; + exec($shellPipe, $result, $returnVal); + + if ($returnVal !== 0) { + // command returned nonzero; that's always an error + $return = PEAR::raiseError(null, SYSTEM_COMMAND_NONZERO_EXIT, null, E_USER_WARNING, null, 'System_Command_Error', true); + } + else if (!$this->options['STDERR']) { + // caller does not care about stderr; return success + $return = implode("\n", $result); + } + else { + // our caller cares about stderr; check stderr output + clearstatcache(); + if (filesize($tmpFile) > 0) { + // the command actually wrote to stderr + $stderr_output = file_get_contents($tmpFile); + $return = PEAR::raiseError(null, SYSTEM_COMMAND_STDERR, null, E_USER_WARNING, $stderr_output, 'System_Command_Error', true); + } else { + // total success; return stdout gathered by exec() + $return = implode("\n", $result); + } + } + + if ((!PEAR::isError($return)) && ($this->options['AUTORESET'])) { + $this->reset(); + } + + unlink($tmpFile); + return $return; + } + } + + // }}} + // {{{ which() + + /** + * Functionality similiar to unix 'which'. Searches the path + * for the specified program. + * + * @param $cmd name of the executable to search for + * + * @access private + * @return string returns the full path if found, false if not + */ + function which($in_cmd) + { + // only pass non-empty strings to System::which() + if (!is_string($in_cmd) || '' === $in_cmd) { + return(false); + } + + // explicitly pass false as fallback value + return System::which($in_cmd, false); + } + + // }}} + // {{{ reset() + + /** + * Prepare for a new command to be built + * + * @access public + * @return void + */ + function reset() + { + $this->previousElement = null; + $this->systemCommand = null; + $this->commandStatus = 0; + } + + // }}} + // {{{ errorMessage() + + /** + * Return a textual error message for a System_Command error code + * + * @param integer error code + * + * @return string error message, or false if the error code was + * not recognized + */ + function errorMessage($in_value) + { + static $errorMessages; + if (!isset($errorMessages)) { + $errorMessages = array( + SYSTEM_COMMAND_OK => 'no error', + SYSTEM_COMMAND_ERROR => 'unknown error', + SYSTEM_COMMAND_NO_SHELL => 'no shell found', + SYSTEM_COMMAND_INVALID_SHELL => 'invalid shell', + SYSTEM_COMMAND_TMPDIR_ERROR => 'could not create temporary directory', + SYSTEM_COMMAND_INVALID_OPERATOR => 'control operator invalid', + SYSTEM_COMMAND_INVALID_COMMAND => 'invalid system command', + SYSTEM_COMMAND_OPERATOR_PLACEMENT => 'invalid placement of control operator', + SYSTEM_COMMAND_COMMAND_PLACEMENT => 'invalid placement of command', + SYSTEM_COMMAND_NOHUP_MISSING => 'nohup not found on system', + SYSTEM_COMMAND_NO_OUTPUT => 'output not allowed', + SYSTEM_COMMAND_STDERR => 'command wrote to stderr', + SYSTEM_COMMAND_NONZERO_EXIT => 'non-zero exit value from command', + ); + } + + if (System_Command::isError($in_value)) { + $in_value = $in_value->getCode(); + } + + return isset($errorMessages[$in_value]) ? $errorMessages[$in_value] : $errorMessages[SYSTEM_COMMAND_ERROR]; + } + + // }}} + // {{{ isError() + + /** + * Tell whether a result code from a System_Command method is an error + * + * @param int result code + * + * @return bool whether $in_value is an error + * + * @access public + */ + function isError($in_value) + { + return (is_object($in_value) && + (strtolower(get_class($in_value)) == 'system_command_error' || + is_subclass_of($in_value, 'system_command_error'))); + } + + // }}} +} + +// {{{ class System_Command_Error + +/** + * System_Command_Error constructor. + * + * @param mixed System_Command error code, or string with error message. + * @param integer what "error mode" to operate in + * @param integer what error level to use for $mode & PEAR_ERROR_TRIGGER + * @param mixed additional debug info, such as the last query + * + * @access public + * + * @see PEAR_Error + */ + +// }}} +class System_Command_Error extends PEAR_Error +{ + // {{{ properties + + /** + * Message in front of the error message + * @var string $error_message_prefix + */ + var $error_message_prefix = 'System_Command Error: '; + + // }}} + // {{{ constructor + + function System_Command_Error($code = SYSTEM_COMMAND_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + if (is_int($code)) { + $this->PEAR_Error(System_Command::errorMessage($code), $code, $mode, $level, $debuginfo); + } else { + $this->PEAR_Error("Invalid error code: $code", SYSTEM_COMMAND_ERROR, $mode, $level, $debuginfo); + } + } + + // }}} +} +?> diff --git a/includes/pear/Text/Diff.php b/includes/pear/Text/Diff.php new file mode 100644 index 0000000..4b3de1b --- /dev/null +++ b/includes/pear/Text/Diff.php @@ -0,0 +1,453 @@ +<?php +/** + * General API for generating and formatting diffs - the differences between + * two sequences of strings. + * + * The original PHP version of this code was written by Geoffrey T. Dairiki + * <dairiki@dairiki.org>, and is used/adapted with his permission. + * + * $Horde: framework/Text_Diff/Diff.php,v 1.11.2.12 2009/01/06 15:23:41 jan Exp $ + * + * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> + * Copyright 2004-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff { + + /** + * Array of changes. + * + * @var array + */ + var $_edits; + + /** + * Computes diffs between sequences of strings. + * + * @param string $engine Name of the diffing engine to use. 'auto' + * will automatically select the best. + * @param array $params Parameters to pass to the diffing engine. + * Normally an array of two arrays, each + * containing the lines from a file. + */ + function Text_Diff($engine, $params) + { + // Backward compatibility workaround. + if (!is_string($engine)) { + $params = array($engine, $params); + $engine = 'auto'; + } + + if ($engine == 'auto') { + $engine = extension_loaded('xdiff') ? 'xdiff' : 'native'; + } else { + $engine = basename($engine); + } + + require_once 'Text/Diff/Engine/' . $engine . '.php'; + $class = 'Text_Diff_Engine_' . $engine; + $diff_engine = new $class(); + + $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); + } + + /** + * Returns the array of differences. + */ + function getDiff() + { + return $this->_edits; + } + + /** + * returns the number of new (added) lines in a given diff. + * + * @since Text_Diff 1.1.0 + * @since Horde 3.2 + * + * @return integer The number of new lines + */ + function countAddedLines() + { + $count = 0; + foreach ($this->_edits as $edit) { + if (is_a($edit, 'Text_Diff_Op_add') || + is_a($edit, 'Text_Diff_Op_change')) { + $count += $edit->nfinal(); + } + } + return $count; + } + + /** + * Returns the number of deleted (removed) lines in a given diff. + * + * @since Text_Diff 1.1.0 + * @since Horde 3.2 + * + * @return integer The number of deleted lines + */ + function countDeletedLines() + { + $count = 0; + foreach ($this->_edits as $edit) { + if (is_a($edit, 'Text_Diff_Op_delete') || + is_a($edit, 'Text_Diff_Op_change')) { + $count += $edit->norig(); + } + } + return $count; + } + + /** + * Computes a reversed diff. + * + * Example: + * <code> + * $diff = new Text_Diff($lines1, $lines2); + * $rev = $diff->reverse(); + * </code> + * + * @return Text_Diff A Diff object representing the inverse of the + * original diff. Note that we purposely don't return a + * reference here, since this essentially is a clone() + * method. + */ + function reverse() + { + if (version_compare(zend_version(), '2', '>')) { + $rev = clone($this); + } else { + $rev = $this; + } + $rev->_edits = array(); + foreach ($this->_edits as $edit) { + $rev->_edits[] = $edit->reverse(); + } + return $rev; + } + + /** + * Checks for an empty diff. + * + * @return boolean True if two sequences were identical. + */ + function isEmpty() + { + foreach ($this->_edits as $edit) { + if (!is_a($edit, 'Text_Diff_Op_copy')) { + return false; + } + } + return true; + } + + /** + * Computes the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposes. + * + * @return integer The length of the LCS. + */ + function lcs() + { + $lcs = 0; + foreach ($this->_edits as $edit) { + if (is_a($edit, 'Text_Diff_Op_copy')) { + $lcs += count($edit->orig); + } + } + return $lcs; + } + + /** + * Gets the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the constructor. + * + * @return array The original sequence of strings. + */ + function getOriginal() + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->orig) { + array_splice($lines, count($lines), 0, $edit->orig); + } + } + return $lines; + } + + /** + * Gets the final set of lines. + * + * This reconstructs the $to_lines parameter passed to the constructor. + * + * @return array The sequence of strings. + */ + function getFinal() + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->final) { + array_splice($lines, count($lines), 0, $edit->final); + } + } + return $lines; + } + + /** + * Removes trailing newlines from a line of text. This is meant to be used + * with array_walk(). + * + * @param string $line The line to trim. + * @param integer $key The index of the line in the array. Not used. + */ + function trimNewlines(&$line, $key) + { + $line = str_replace(array("\n", "\r"), '', $line); + } + + /** + * Determines the location of the system temporary directory. + * + * @static + * + * @access protected + * + * @return string A directory name which can be used for temp files. + * Returns false if one could not be found. + */ + function _getTempDir() + { + $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp', + 'c:\windows\temp', 'c:\winnt\temp'); + + /* Try PHP's upload_tmp_dir directive. */ + $tmp = ini_get('upload_tmp_dir'); + + /* Otherwise, try to determine the TMPDIR environment variable. */ + if (!strlen($tmp)) { + $tmp = getenv('TMPDIR'); + } + + /* If we still cannot determine a value, then cycle through a list of + * preset possibilities. */ + while (!strlen($tmp) && count($tmp_locations)) { + $tmp_check = array_shift($tmp_locations); + if (@is_dir($tmp_check)) { + $tmp = $tmp_check; + } + } + + /* If it is still empty, we have failed, so return false; otherwise + * return the directory determined. */ + return strlen($tmp) ? $tmp : false; + } + + /** + * Checks a diff for validity. + * + * This is here only for debugging purposes. + */ + function _check($from_lines, $to_lines) + { + if (serialize($from_lines) != serialize($this->getOriginal())) { + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); + } + if (serialize($to_lines) != serialize($this->getFinal())) { + trigger_error("Reconstructed final doesn't match", E_USER_ERROR); + } + + $rev = $this->reverse(); + if (serialize($to_lines) != serialize($rev->getOriginal())) { + trigger_error("Reversed original doesn't match", E_USER_ERROR); + } + if (serialize($from_lines) != serialize($rev->getFinal())) { + trigger_error("Reversed final doesn't match", E_USER_ERROR); + } + + $prevtype = null; + foreach ($this->_edits as $edit) { + if ($prevtype == get_class($edit)) { + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); + } + $prevtype = get_class($edit); + } + + return true; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_MappedDiff extends Text_Diff { + + /** + * Computes a diff between sequences of strings. + * + * This can be used to compute things like case-insensitve diffs, or diffs + * which ignore changes in white-space. + * + * @param array $from_lines An array of strings. + * @param array $to_lines An array of strings. + * @param array $mapped_from_lines This array should have the same size + * number of elements as $from_lines. The + * elements in $mapped_from_lines and + * $mapped_to_lines are what is actually + * compared when computing the diff. + * @param array $mapped_to_lines This array should have the same number + * of elements as $to_lines. + */ + function Text_MappedDiff($from_lines, $to_lines, + $mapped_from_lines, $mapped_to_lines) + { + assert(count($from_lines) == count($mapped_from_lines)); + assert(count($to_lines) == count($mapped_to_lines)); + + parent::Text_Diff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < count($this->_edits); $i++) { + $orig = &$this->_edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, count($orig)); + $xi += count($orig); + } + + $final = &$this->_edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, count($final)); + $yi += count($final); + } + } + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op { + + var $orig; + var $final; + + function &reverse() + { + trigger_error('Abstract method', E_USER_ERROR); + } + + function norig() + { + return $this->orig ? count($this->orig) : 0; + } + + function nfinal() + { + return $this->final ? count($this->final) : 0; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_copy extends Text_Diff_Op { + + function Text_Diff_Op_copy($orig, $final = false) + { + if (!is_array($final)) { + $final = $orig; + } + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = &new Text_Diff_Op_copy($this->final, $this->orig); + return $reverse; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_delete extends Text_Diff_Op { + + function Text_Diff_Op_delete($lines) + { + $this->orig = $lines; + $this->final = false; + } + + function &reverse() + { + $reverse = &new Text_Diff_Op_add($this->orig); + return $reverse; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_add extends Text_Diff_Op { + + function Text_Diff_Op_add($lines) + { + $this->final = $lines; + $this->orig = false; + } + + function &reverse() + { + $reverse = &new Text_Diff_Op_delete($this->final); + return $reverse; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_change extends Text_Diff_Op { + + function Text_Diff_Op_change($orig, $final) + { + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = &new Text_Diff_Op_change($this->final, $this->orig); + return $reverse; + } + +} diff --git a/includes/pear/Text/Diff/Engine/native.php b/includes/pear/Text/Diff/Engine/native.php new file mode 100644 index 0000000..84168c0 --- /dev/null +++ b/includes/pear/Text/Diff/Engine/native.php @@ -0,0 +1,438 @@ +<?php +/** + * Class used internally by Text_Diff to actually compute the diffs. + * + * This class is implemented using native PHP code. + * + * The algorithm used here is mostly lifted from the perl module + * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: + * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip + * + * More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html + * + * Some ideas (and a bit of code) are taken from analyze.c, of GNU + * diffutils-2.7, which can be found at: + * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz + * + * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from + * Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this + * code was written by him, and is used/adapted with his permission. + * + * $Horde: framework/Text_Diff/Diff/Engine/native.php,v 1.7.2.5 2009/01/06 15:23:41 jan Exp $ + * + * Copyright 2004-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * @package Text_Diff + */ +class Text_Diff_Engine_native { + + function diff($from_lines, $to_lines) + { + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); + + $n_from = count($from_lines); + $n_to = count($to_lines); + + $this->xchanged = $this->ychanged = array(); + $this->xv = $this->yv = array(); + $this->xind = $this->yind = array(); + unset($this->seq); + unset($this->in_seq); + unset($this->lcs); + + // Skip leading common lines. + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { + if ($from_lines[$skip] !== $to_lines[$skip]) { + break; + } + $this->xchanged[$skip] = $this->ychanged[$skip] = false; + } + + // Skip trailing common lines. + $xi = $n_from; $yi = $n_to; + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { + if ($from_lines[$xi] !== $to_lines[$yi]) { + break; + } + $this->xchanged[$xi] = $this->ychanged[$yi] = false; + } + + // Ignore lines which do not exist in both files. + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $xhash[$from_lines[$xi]] = 1; + } + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { + $line = $to_lines[$yi]; + if (($this->ychanged[$yi] = empty($xhash[$line]))) { + continue; + } + $yhash[$line] = 1; + $this->yv[] = $line; + $this->yind[] = $yi; + } + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $line = $from_lines[$xi]; + if (($this->xchanged[$xi] = empty($yhash[$line]))) { + continue; + } + $this->xv[] = $line; + $this->xind[] = $xi; + } + + // Find the LCS. + $this->_compareseq(0, count($this->xv), 0, count($this->yv)); + + // Merge edits when possible. + $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged); + + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + while ($xi < $n_from || $yi < $n_to) { + assert($yi < $n_to || $this->xchanged[$xi]); + assert($xi < $n_from || $this->ychanged[$yi]); + + // Skip matching "snake". + $copy = array(); + while ($xi < $n_from && $yi < $n_to + && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { + $copy[] = $from_lines[$xi++]; + ++$yi; + } + if ($copy) { + $edits[] = &new Text_Diff_Op_copy($copy); + } + + // Find deletes & adds. + $delete = array(); + while ($xi < $n_from && $this->xchanged[$xi]) { + $delete[] = $from_lines[$xi++]; + } + + $add = array(); + while ($yi < $n_to && $this->ychanged[$yi]) { + $add[] = $to_lines[$yi++]; + } + + if ($delete && $add) { + $edits[] = &new Text_Diff_Op_change($delete, $add); + } elseif ($delete) { + $edits[] = &new Text_Diff_Op_delete($delete); + } elseif ($add) { + $edits[] = &new Text_Diff_Op_add($add); + } + } + + return $edits; + } + + /** + * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, + * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized + * segments. + * + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of + * NCHUNKS+1 (X, Y) indexes giving the diving points between sub + * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), + * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == + * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). + * + * This function assumes that the first lines of the specified portions of + * the two files do not match, and likewise that the last lines do not + * match. The caller must trim matching lines from the beginning and end + * of the portions it is going to specify. + */ + function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) + { + $flip = false; + + if ($xlim - $xoff > $ylim - $yoff) { + /* Things seems faster (I'm not sure I understand why) when the + * shortest sequence is in X. */ + $flip = true; + list ($xoff, $xlim, $yoff, $ylim) + = array($yoff, $ylim, $xoff, $xlim); + } + + if ($flip) { + for ($i = $ylim - 1; $i >= $yoff; $i--) { + $ymatches[$this->xv[$i]][] = $i; + } + } else { + for ($i = $ylim - 1; $i >= $yoff; $i--) { + $ymatches[$this->yv[$i]][] = $i; + } + } + + $this->lcs = 0; + $this->seq[0]= $yoff - 1; + $this->in_seq = array(); + $ymids[0] = array(); + + $numer = $xlim - $xoff + $nchunks - 1; + $x = $xoff; + for ($chunk = 0; $chunk < $nchunks; $chunk++) { + if ($chunk > 0) { + for ($i = 0; $i <= $this->lcs; $i++) { + $ymids[$i][$chunk - 1] = $this->seq[$i]; + } + } + + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); + for (; $x < $x1; $x++) { + $line = $flip ? $this->yv[$x] : $this->xv[$x]; + if (empty($ymatches[$line])) { + continue; + } + $matches = $ymatches[$line]; + reset($matches); + while (list(, $y) = each($matches)) { + if (empty($this->in_seq[$y])) { + $k = $this->_lcsPos($y); + assert($k > 0); + $ymids[$k] = $ymids[$k - 1]; + break; + } + } + while (list(, $y) = each($matches)) { + if ($y > $this->seq[$k - 1]) { + assert($y <= $this->seq[$k]); + /* Optimization: this is a common case: next match is + * just replacing previous match. */ + $this->in_seq[$this->seq[$k]] = false; + $this->seq[$k] = $y; + $this->in_seq[$y] = 1; + } elseif (empty($this->in_seq[$y])) { + $k = $this->_lcsPos($y); + assert($k > 0); + $ymids[$k] = $ymids[$k - 1]; + } + } + } + } + + $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); + $ymid = $ymids[$this->lcs]; + for ($n = 0; $n < $nchunks - 1; $n++) { + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + } + $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); + + return array($this->lcs, $seps); + } + + function _lcsPos($ypos) + { + $end = $this->lcs; + if ($end == 0 || $ypos > $this->seq[$end]) { + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + return $this->lcs; + } + + $beg = 1; + while ($beg < $end) { + $mid = (int)(($beg + $end) / 2); + if ($ypos > $this->seq[$mid]) { + $beg = $mid + 1; + } else { + $end = $mid; + } + } + + assert($ypos != $this->seq[$end]); + + $this->in_seq[$this->seq[$end]] = false; + $this->seq[$end] = $ypos; + $this->in_seq[$ypos] = 1; + return $end; + } + + /** + * Finds LCS of two sequences. + * + * The results are recorded in the vectors $this->{x,y}changed[], by + * storing a 1 in the element for each line that is an insertion or + * deletion (ie. is not in the LCS). + * + * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. + * + * Note that XLIM, YLIM are exclusive bounds. All line numbers are + * origin-0 and discarded lines are not counted. + */ + function _compareseq ($xoff, $xlim, $yoff, $ylim) + { + /* Slide down the bottom initial diagonal. */ + while ($xoff < $xlim && $yoff < $ylim + && $this->xv[$xoff] == $this->yv[$yoff]) { + ++$xoff; + ++$yoff; + } + + /* Slide up the top initial diagonal. */ + while ($xlim > $xoff && $ylim > $yoff + && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { + --$xlim; + --$ylim; + } + + if ($xoff == $xlim || $yoff == $ylim) { + $lcs = 0; + } else { + /* This is ad hoc but seems to work well. $nchunks = + * sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks = + * max(2,min(8,(int)$nchunks)); */ + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list($lcs, $seps) + = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); + } + + if ($lcs == 0) { + /* X and Y sequences have no common subsequence: mark all + * changed. */ + while ($yoff < $ylim) { + $this->ychanged[$this->yind[$yoff++]] = 1; + } + while ($xoff < $xlim) { + $this->xchanged[$this->xind[$xoff++]] = 1; + } + } else { + /* Use the partitions to split this problem into subproblems. */ + reset($seps); + $pt1 = $seps[0]; + while ($pt2 = next($seps)) { + $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); + $pt1 = $pt2; + } + } + } + + /** + * Adjusts inserts/deletes of identical lines to join changes as much as + * possible. + * + * We do something when a run of changed lines include a line at one end + * and has an excluded, identical line at the other. We are free to + * choose which identical line is included. `compareseq' usually chooses + * the one at the beginning, but usually it is cleaner to consider the + * following identical line to be the "change". + * + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). + */ + function _shiftBoundaries($lines, &$changed, $other_changed) + { + $i = 0; + $j = 0; + + assert('count($lines) == count($changed)'); + $len = count($lines); + $other_len = count($other_changed); + + while (1) { + /* Scan forward to find the beginning of another run of + * changes. Also keep track of the corresponding point in the + * other file. + * + * Throughout this code, $i and $j are adjusted together so that + * the first $i elements of $changed and the first $j elements of + * $other_changed both contain the same number of zeros (unchanged + * lines). + * + * Furthermore, $j is always kept so that $j == $other_len or + * $other_changed[$j] == false. */ + while ($j < $other_len && $other_changed[$j]) { + $j++; + } + + while ($i < $len && ! $changed[$i]) { + assert('$j < $other_len && ! $other_changed[$j]'); + $i++; $j++; + while ($j < $other_len && $other_changed[$j]) { + $j++; + } + } + + if ($i == $len) { + break; + } + + $start = $i; + + /* Find the end of this run of changes. */ + while (++$i < $len && $changed[$i]) { + continue; + } + + do { + /* Record the length of this run of changes, so that we can + * later determine whether the run has grown. */ + $runlength = $i - $start; + + /* Move the changed region back, so long as the previous + * unchanged line matches the last changed one. This merges + * with previous changed regions. */ + while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { + $changed[--$start] = 1; + $changed[--$i] = false; + while ($start > 0 && $changed[$start - 1]) { + $start--; + } + assert('$j > 0'); + while ($other_changed[--$j]) { + continue; + } + assert('$j >= 0 && !$other_changed[$j]'); + } + + /* Set CORRESPONDING to the end of the changed run, at the + * last point where it corresponds to a changed run in the + * other file. CORRESPONDING == LEN means no such point has + * been found. */ + $corresponding = $j < $other_len ? $i : $len; + + /* Move the changed region forward, so long as the first + * changed line matches the following unchanged one. This + * merges with following changed regions. Do this second, so + * that if there are no merges, the changed region is moved + * forward as far as possible. */ + while ($i < $len && $lines[$start] == $lines[$i]) { + $changed[$start++] = false; + $changed[$i++] = 1; + while ($i < $len && $changed[$i]) { + $i++; + } + + assert('$j < $other_len && ! $other_changed[$j]'); + $j++; + if ($j < $other_len && $other_changed[$j]) { + $corresponding = $i; + while ($j < $other_len && $other_changed[$j]) { + $j++; + } + } + } + } while ($runlength != $i - $start); + + /* If possible, move the fully-merged run of changes back to a + * corresponding run in the other file. */ + while ($corresponding < $i) { + $changed[--$start] = 1; + $changed[--$i] = 0; + assert('$j > 0'); + while ($other_changed[--$j]) { + continue; + } + assert('$j >= 0 && !$other_changed[$j]'); + } + } + } + +} diff --git a/includes/pear/Text/Diff/Engine/shell.php b/includes/pear/Text/Diff/Engine/shell.php new file mode 100644 index 0000000..7f858cb --- /dev/null +++ b/includes/pear/Text/Diff/Engine/shell.php @@ -0,0 +1,164 @@ +<?php +/** + * Class used internally by Diff to actually compute the diffs. + * + * This class uses the Unix `diff` program via shell_exec to compute the + * differences between the two input arrays. + * + * $Horde: framework/Text_Diff/Diff/Engine/shell.php,v 1.6.2.4 2009/01/06 15:23:41 jan Exp $ + * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Milian Wolff <mail@milianw.de> + * @package Text_Diff + * @since 0.3.0 + */ +class Text_Diff_Engine_shell { + + /** + * Path to the diff executable + * + * @var string + */ + var $_diffCommand = 'diff'; + + /** + * Returns the array of differences. + * + * @param array $from_lines lines of text from old file + * @param array $to_lines lines of text from new file + * + * @return array all changes made (array with Text_Diff_Op_* objects) + */ + function diff($from_lines, $to_lines) + { + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); + + $temp_dir = Text_Diff::_getTempDir(); + + // Execute gnu diff or similar to get a standard diff file. + $from_file = tempnam($temp_dir, 'Text_Diff'); + $to_file = tempnam($temp_dir, 'Text_Diff'); + $fp = fopen($from_file, 'w'); + fwrite($fp, implode("\n", $from_lines)); + fclose($fp); + $fp = fopen($to_file, 'w'); + fwrite($fp, implode("\n", $to_lines)); + fclose($fp); + $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); + unlink($from_file); + unlink($to_file); + + if (is_null($diff)) { + // No changes were made + return array(new Text_Diff_Op_copy($from_lines)); + } + + $from_line_no = 1; + $to_line_no = 1; + $edits = array(); + + // Get changed lines by parsing something like: + // 0a1,2 + // 1,2c4,6 + // 1,5d6 + preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, + $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + if (!isset($match[5])) { + // This paren is not set every time (see regex). + $match[5] = false; + } + + if ($match[3] == 'a') { + $from_line_no--; + } + + if ($match[3] == 'd') { + $to_line_no--; + } + + if ($from_line_no < $match[1] || $to_line_no < $match[4]) { + // copied lines + assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); + array_push($edits, + new Text_Diff_Op_copy( + $this->_getLines($from_lines, $from_line_no, $match[1] - 1), + $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); + } + + switch ($match[3]) { + case 'd': + // deleted lines + array_push($edits, + new Text_Diff_Op_delete( + $this->_getLines($from_lines, $from_line_no, $match[2]))); + $to_line_no++; + break; + + case 'c': + // changed lines + array_push($edits, + new Text_Diff_Op_change( + $this->_getLines($from_lines, $from_line_no, $match[2]), + $this->_getLines($to_lines, $to_line_no, $match[5]))); + break; + + case 'a': + // added lines + array_push($edits, + new Text_Diff_Op_add( + $this->_getLines($to_lines, $to_line_no, $match[5]))); + $from_line_no++; + break; + } + } + + if (!empty($from_lines)) { + // Some lines might still be pending. Add them as copied + array_push($edits, + new Text_Diff_Op_copy( + $this->_getLines($from_lines, $from_line_no, + $from_line_no + count($from_lines) - 1), + $this->_getLines($to_lines, $to_line_no, + $to_line_no + count($to_lines) - 1))); + } + + return $edits; + } + + /** + * Get lines from either the old or new text + * + * @access private + * + * @param array &$text_lines Either $from_lines or $to_lines + * @param int &$line_no Current line number + * @param int $end Optional end line, when we want to chop more + * than one line. + * + * @return array The chopped lines + */ + function _getLines(&$text_lines, &$line_no, $end = false) + { + if (!empty($end)) { + $lines = array(); + // We can shift even more + while ($line_no <= $end) { + array_push($lines, array_shift($text_lines)); + $line_no++; + } + } else { + $lines = array(array_shift($text_lines)); + $line_no++; + } + + return $lines; + } + +} diff --git a/includes/pear/Text/Diff/Engine/string.php b/includes/pear/Text/Diff/Engine/string.php new file mode 100644 index 0000000..9352e60 --- /dev/null +++ b/includes/pear/Text/Diff/Engine/string.php @@ -0,0 +1,250 @@ +<?php +/** + * Parses unified or context diffs output from eg. the diff utility. + * + * Example: + * <code> + * $patch = file_get_contents('example.patch'); + * $diff = new Text_Diff('string', array($patch)); + * $renderer = new Text_Diff_Renderer_inline(); + * echo $renderer->render($diff); + * </code> + * + * $Horde: framework/Text_Diff/Diff/Engine/string.php,v 1.5.2.7 2009/07/24 13:04:43 jan Exp $ + * + * Copyright 2005 Örjan Persson <o@42mm.org> + * Copyright 2005-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Örjan Persson <o@42mm.org> + * @package Text_Diff + * @since 0.2.0 + */ +class Text_Diff_Engine_string { + + /** + * Parses a unified or context diff. + * + * First param contains the whole diff and the second can be used to force + * a specific diff type. If the second parameter is 'autodetect', the + * diff will be examined to find out which type of diff this is. + * + * @param string $diff The diff content. + * @param string $mode The diff mode of the content in $diff. One of + * 'context', 'unified', or 'autodetect'. + * + * @return array List of all diff operations. + */ + function diff($diff, $mode = 'autodetect') + { + // Detect line breaks. + $lnbr = "\n"; + if (strpos($diff, "\r\n") !== false) { + $lnbr = "\r\n"; + } elseif (strpos($diff, "\r") !== false) { + $lnbr = "\r"; + } + + // Make sure we have a line break at the EOF. + if (substr($diff, -strlen($lnbr)) != $lnbr) { + $diff .= $lnbr; + } + + if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { + return PEAR::raiseError('Type of diff is unsupported'); + } + + if ($mode == 'autodetect') { + $context = strpos($diff, '***'); + $unified = strpos($diff, '---'); + if ($context === $unified) { + return PEAR::raiseError('Type of diff could not be detected'); + } elseif ($context === false || $unified === false) { + $mode = $context !== false ? 'context' : 'unified'; + } else { + $mode = $context < $unified ? 'context' : 'unified'; + } + } + + // Split by new line and remove the diff header, if there is one. + $diff = explode($lnbr, $diff); + if (($mode == 'context' && strpos($diff[0], '***') === 0) || + ($mode == 'unified' && strpos($diff[0], '---') === 0)) { + array_shift($diff); + array_shift($diff); + } + + if ($mode == 'context') { + return $this->parseContextDiff($diff); + } else { + return $this->parseUnifiedDiff($diff); + } + } + + /** + * Parses an array containing the unified diff. + * + * @param array $diff Array of lines. + * + * @return array List of all diff operations. + */ + function parseUnifiedDiff($diff) + { + $edits = array(); + $end = count($diff) - 1; + for ($i = 0; $i < $end;) { + $diff1 = array(); + switch (substr($diff[$i], 0, 1)) { + case ' ': + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); + $edits[] = new Text_Diff_Op_copy($diff1); + break; + + case '+': + // get all new lines + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff1); + break; + + case '-': + // get changed or removed lines + $diff2 = array(); + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); + + while ($i < $end && substr($diff[$i], 0, 1) == '+') { + $diff2[] = substr($diff[$i++], 1); + } + if (count($diff2) == 0) { + $edits[] = new Text_Diff_Op_delete($diff1); + } else { + $edits[] = new Text_Diff_Op_change($diff1, $diff2); + } + break; + + default: + $i++; + break; + } + } + + return $edits; + } + + /** + * Parses an array containing the context diff. + * + * @param array $diff Array of lines. + * + * @return array List of all diff operations. + */ + function parseContextDiff(&$diff) + { + $edits = array(); + $i = $max_i = $j = $max_j = 0; + $end = count($diff) - 1; + while ($i < $end && $j < $end) { + while ($i >= $max_i && $j >= $max_j) { + // Find the boundaries of the diff output of the two files + for ($i = $j; + $i < $end && substr($diff[$i], 0, 3) == '***'; + $i++); + for ($max_i = $i; + $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; + $max_i++); + for ($j = $max_i; + $j < $end && substr($diff[$j], 0, 3) == '---'; + $j++); + for ($max_j = $j; + $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; + $max_j++); + } + + // find what hasn't been changed + $array = array(); + while ($i < $max_i && + $j < $max_j && + strcmp($diff[$i], $diff[$j]) == 0) { + $array[] = substr($diff[$i], 2); + $i++; + $j++; + } + + while ($i < $max_i && ($max_j-$j) <= 1) { + if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { + break; + } + $array[] = substr($diff[$i++], 2); + } + + while ($j < $max_j && ($max_i-$i) <= 1) { + if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { + break; + } + $array[] = substr($diff[$j++], 2); + } + if (count($array) > 0) { + $edits[] = new Text_Diff_Op_copy($array); + } + + if ($i < $max_i) { + $diff1 = array(); + switch (substr($diff[$i], 0, 1)) { + case '!': + $diff2 = array(); + do { + $diff1[] = substr($diff[$i], 2); + if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { + $diff2[] = substr($diff[$j++], 2); + } + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); + $edits[] = new Text_Diff_Op_change($diff1, $diff2); + break; + + case '+': + do { + $diff1[] = substr($diff[$i], 2); + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff1); + break; + + case '-': + do { + $diff1[] = substr($diff[$i], 2); + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); + $edits[] = new Text_Diff_Op_delete($diff1); + break; + } + } + + if ($j < $max_j) { + $diff2 = array(); + switch (substr($diff[$j], 0, 1)) { + case '+': + do { + $diff2[] = substr($diff[$j++], 2); + } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff2); + break; + + case '-': + do { + $diff2[] = substr($diff[$j++], 2); + } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); + $edits[] = new Text_Diff_Op_delete($diff2); + break; + } + } + } + + return $edits; + } + +} diff --git a/includes/pear/Text/Diff/Engine/xdiff.php b/includes/pear/Text/Diff/Engine/xdiff.php new file mode 100644 index 0000000..241d2e5 --- /dev/null +++ b/includes/pear/Text/Diff/Engine/xdiff.php @@ -0,0 +1,66 @@ +<?php +/** + * Class used internally by Diff to actually compute the diffs. + * + * This class uses the xdiff PECL package (http://pecl.php.net/package/xdiff) + * to compute the differences between the two input arrays. + * + * $Horde: framework/Text_Diff/Diff/Engine/xdiff.php,v 1.4.2.5 2009/07/24 13:06:24 jan Exp $ + * + * Copyright 2004-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Jon Parise <jon@horde.org> + * @package Text_Diff + */ +class Text_Diff_Engine_xdiff { + + /** + */ + function diff($from_lines, $to_lines) + { + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); + + /* Convert the two input arrays into strings for xdiff processing. */ + $from_string = implode("\n", $from_lines); + $to_string = implode("\n", $to_lines); + + /* Diff the two strings and convert the result to an array. */ + $diff = xdiff_string_diff($from_string, $to_string, count($to_lines)); + $diff = explode("\n", $diff); + + /* Walk through the diff one line at a time. We build the $edits + * array of diff operations by reading the first character of the + * xdiff output (which is in the "unified diff" format). + * + * Note that we don't have enough information to detect "changed" + * lines using this approach, so we can't add Text_Diff_Op_changed + * instances to the $edits array. The result is still perfectly + * valid, albeit a little less descriptive and efficient. */ + $edits = array(); + foreach ($diff as $line) { + if (!strlen($line)) { + continue; + } + switch ($line[0]) { + case ' ': + $edits[] = &new Text_Diff_Op_copy(array(substr($line, 1))); + break; + + case '+': + $edits[] = &new Text_Diff_Op_add(array(substr($line, 1))); + break; + + case '-': + $edits[] = &new Text_Diff_Op_delete(array(substr($line, 1))); + break; + } + } + + return $edits; + } + +} diff --git a/includes/pear/Text/Diff/Mapped.php b/includes/pear/Text/Diff/Mapped.php new file mode 100644 index 0000000..dc46e5e --- /dev/null +++ b/includes/pear/Text/Diff/Mapped.php @@ -0,0 +1,55 @@ +<?php +/** + * $Horde: framework/Text_Diff/Diff/Mapped.php,v 1.3.2.4 2009/01/06 15:23:41 jan Exp $ + * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff_Mapped extends Text_Diff { + + /** + * Computes a diff between sequences of strings. + * + * This can be used to compute things like case-insensitve diffs, or diffs + * which ignore changes in white-space. + * + * @param array $from_lines An array of strings. + * @param array $to_lines An array of strings. + * @param array $mapped_from_lines This array should have the same size + * number of elements as $from_lines. The + * elements in $mapped_from_lines and + * $mapped_to_lines are what is actually + * compared when computing the diff. + * @param array $mapped_to_lines This array should have the same number + * of elements as $to_lines. + */ + function Text_Diff_Mapped($from_lines, $to_lines, + $mapped_from_lines, $mapped_to_lines) + { + assert(count($from_lines) == count($mapped_from_lines)); + assert(count($to_lines) == count($mapped_to_lines)); + + parent::Text_Diff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < count($this->_edits); $i++) { + $orig = &$this->_edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, count($orig)); + $xi += count($orig); + } + + $final = &$this->_edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, count($final)); + $yi += count($final); + } + } + } + +} diff --git a/includes/pear/Text/Diff/Renderer.php b/includes/pear/Text/Diff/Renderer.php new file mode 100644 index 0000000..3a51650 --- /dev/null +++ b/includes/pear/Text/Diff/Renderer.php @@ -0,0 +1,237 @@ +<?php +/** + * A class to render Diffs in different formats. + * + * This class renders the diff in classic diff format. It is intended that + * this class be customized via inheritance, to obtain fancier outputs. + * + * $Horde: framework/Text_Diff/Diff/Renderer.php,v 1.5.10.12 2009/07/24 13:26:40 jan Exp $ + * + * Copyright 2004-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + */ +class Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_leading_context_lines = 0; + + /** + * Number of trailing context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_trailing_context_lines = 0; + + /** + * Constructor. + */ + function Text_Diff_Renderer($params = array()) + { + foreach ($params as $param => $value) { + $v = '_' . $param; + if (isset($this->$v)) { + $this->$v = $value; + } + } + } + + /** + * Get any renderer parameters. + * + * @return array All parameters of this renderer object. + */ + function getParams() + { + $params = array(); + foreach (get_object_vars($this) as $k => $v) { + if ($k[0] == '_') { + $params[substr($k, 1)] = $v; + } + } + + return $params; + } + + /** + * Renders a diff. + * + * @param Text_Diff $diff A Text_Diff object. + * + * @return string The formatted output. + */ + function render($diff) + { + $xi = $yi = 1; + $block = false; + $context = array(); + + $nlead = $this->_leading_context_lines; + $ntrail = $this->_trailing_context_lines; + + $output = $this->_startDiff(); + + $diffs = $diff->getDiff(); + foreach ($diffs as $i => $edit) { + /* If these are unchanged (copied) lines, and we want to keep + * leading or trailing context lines, extract them from the copy + * block. */ + if (is_a($edit, 'Text_Diff_Op_copy')) { + /* Do we have any diff blocks yet? */ + if (is_array($block)) { + /* How many lines to keep as context from the copy + * block. */ + $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; + if (count($edit->orig) <= $keep) { + /* We have less lines in the block than we want for + * context => keep the whole block. */ + $block[] = $edit; + } else { + if ($ntrail) { + /* Create a new block with as many lines as we need + * for the trailing context. */ + $context = array_slice($edit->orig, 0, $ntrail); + $block[] = new Text_Diff_Op_copy($context); + } + /* @todo */ + $output .= $this->_block($x0, $ntrail + $xi - $x0, + $y0, $ntrail + $yi - $y0, + $block); + $block = false; + } + } + /* Keep the copy block as the context for the next block. */ + $context = $edit->orig; + } else { + /* Don't we have any diff blocks yet? */ + if (!is_array($block)) { + /* Extract context lines from the preceding copy block. */ + $context = array_slice($context, count($context) - $nlead); + $x0 = $xi - count($context); + $y0 = $yi - count($context); + $block = array(); + if ($context) { + $block[] = new Text_Diff_Op_copy($context); + } + } + $block[] = $edit; + } + + if ($edit->orig) { + $xi += count($edit->orig); + } + if ($edit->final) { + $yi += count($edit->final); + } + } + + if (is_array($block)) { + $output .= $this->_block($x0, $xi - $x0, + $y0, $yi - $y0, + $block); + } + + return $output . $this->_endDiff(); + } + + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) + { + $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen)); + + foreach ($edits as $edit) { + switch (strtolower(get_class($edit))) { + case 'text_diff_op_copy': + $output .= $this->_context($edit->orig); + break; + + case 'text_diff_op_add': + $output .= $this->_added($edit->final); + break; + + case 'text_diff_op_delete': + $output .= $this->_deleted($edit->orig); + break; + + case 'text_diff_op_change': + $output .= $this->_changed($edit->orig, $edit->final); + break; + } + } + + return $output . $this->_endBlock(); + } + + function _startDiff() + { + return ''; + } + + function _endDiff() + { + return ''; + } + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen > 1) { + $xbeg .= ',' . ($xbeg + $xlen - 1); + } + if ($ylen > 1) { + $ybeg .= ',' . ($ybeg + $ylen - 1); + } + + // this matches the GNU Diff behaviour + if ($xlen && !$ylen) { + $ybeg--; + } elseif (!$xlen) { + $xbeg--; + } + + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; + } + + function _startBlock($header) + { + return $header . "\n"; + } + + function _endBlock() + { + return ''; + } + + function _lines($lines, $prefix = ' ') + { + return $prefix . implode("\n$prefix", $lines) . "\n"; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '> '); + } + + function _deleted($lines) + { + return $this->_lines($lines, '< '); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . "---\n" . $this->_added($final); + } + +} diff --git a/includes/pear/Text/Diff/Renderer/context.php b/includes/pear/Text/Diff/Renderer/context.php new file mode 100644 index 0000000..af53801 --- /dev/null +++ b/includes/pear/Text/Diff/Renderer/context.php @@ -0,0 +1,77 @@ +<?php +/** + * "Context" diff renderer. + * + * This class renders the diff in classic "context diff" format. + * + * $Horde: framework/Text_Diff/Diff/Renderer/context.php,v 1.3.2.4 2009/01/06 15:23:42 jan Exp $ + * + * Copyright 2004-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + */ + +/** Text_Diff_Renderer */ +require_once 'Text/Diff/Renderer.php'; + +/** + * @package Text_Diff + */ +class Text_Diff_Renderer_context extends Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + */ + var $_leading_context_lines = 4; + + /** + * Number of trailing context "lines" to preserve. + */ + var $_trailing_context_lines = 4; + + var $_second_block = ''; + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) { + $xbeg .= ',' . $xlen; + } + if ($ylen != 1) { + $ybeg .= ',' . $ylen; + } + $this->_second_block = "--- $ybeg ----\n"; + return "***************\n*** $xbeg ****"; + } + + function _endBlock() + { + return $this->_second_block; + } + + function _context($lines) + { + $this->_second_block .= $this->_lines($lines, ' '); + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + $this->_second_block .= $this->_lines($lines, '+ '); + return ''; + } + + function _deleted($lines) + { + return $this->_lines($lines, '- '); + } + + function _changed($orig, $final) + { + $this->_second_block .= $this->_lines($final, '! '); + return $this->_lines($orig, '! '); + } + +} diff --git a/includes/pear/Text/Diff/Renderer/inline.php b/includes/pear/Text/Diff/Renderer/inline.php new file mode 100644 index 0000000..5dd20d2 --- /dev/null +++ b/includes/pear/Text/Diff/Renderer/inline.php @@ -0,0 +1,172 @@ +<?php +/** + * "Inline" diff renderer. + * + * $Horde: framework/Text_Diff/Diff/Renderer/inline.php,v 1.4.10.16 2009/07/24 13:25:29 jan Exp $ + * + * Copyright 2004-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Ciprian Popovici + * @package Text_Diff + */ + +/** Text_Diff_Renderer */ +require_once 'Text/Diff/Renderer.php'; + +/** + * "Inline" diff renderer. + * + * This class renders diffs in the Wiki-style "inline" format. + * + * @author Ciprian Popovici + * @package Text_Diff + */ +class Text_Diff_Renderer_inline extends Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + */ + var $_leading_context_lines = 10000; + + /** + * Number of trailing context "lines" to preserve. + */ + var $_trailing_context_lines = 10000; + + /** + * Prefix for inserted text. + */ + var $_ins_prefix = '<ins>'; + + /** + * Suffix for inserted text. + */ + var $_ins_suffix = '</ins>'; + + /** + * Prefix for deleted text. + */ + var $_del_prefix = '<del>'; + + /** + * Suffix for deleted text. + */ + var $_del_suffix = '</del>'; + + /** + * Header for each change block. + */ + var $_block_header = ''; + + /** + * What are we currently splitting on? Used to recurse to show word-level + * changes. + */ + var $_split_level = 'lines'; + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + return $this->_block_header; + } + + function _startBlock($header) + { + return $header; + } + + function _lines($lines, $prefix = ' ', $encode = true) + { + if ($encode) { + array_walk($lines, array(&$this, '_encode')); + } + + if ($this->_split_level == 'words') { + return implode('', $lines); + } else { + return implode("\n", $lines) . "\n"; + } + } + + function _added($lines) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_ins_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_ins_suffix; + return $this->_lines($lines, ' ', false); + } + + function _deleted($lines, $words = false) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_del_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_del_suffix; + return $this->_lines($lines, ' ', false); + } + + function _changed($orig, $final) + { + /* If we've already split on words, don't try to do so again - just + * display. */ + if ($this->_split_level == 'words') { + $prefix = ''; + while ($orig[0] !== false && $final[0] !== false && + substr($orig[0], 0, 1) == ' ' && + substr($final[0], 0, 1) == ' ') { + $prefix .= substr($orig[0], 0, 1); + $orig[0] = substr($orig[0], 1); + $final[0] = substr($final[0], 1); + } + return $prefix . $this->_deleted($orig) . $this->_added($final); + } + + $text1 = implode("\n", $orig); + $text2 = implode("\n", $final); + + /* Non-printing newline marker. */ + $nl = "\0"; + + /* We want to split on word boundaries, but we need to + * preserve whitespace as well. Therefore we split on words, + * but include all blocks of whitespace in the wordlist. */ + $diff = new Text_Diff('native', + array($this->_splitOnWords($text1, $nl), + $this->_splitOnWords($text2, $nl))); + + /* Get the diff in inline format. */ + $renderer = new Text_Diff_Renderer_inline + (array_merge($this->getParams(), + array('split_level' => 'words'))); + + /* Run the diff and get the output. */ + return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; + } + + function _splitOnWords($string, $newlineEscape = "\n") + { + // Ignore \0; otherwise the while loop will never finish. + $string = str_replace("\0", '', $string); + + $words = array(); + $length = strlen($string); + $pos = 0; + + while ($pos < $length) { + // Eat a word with any preceding whitespace. + $spaces = strspn(substr($string, $pos), " \n"); + $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); + $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); + $pos += $spaces + $nextpos; + } + + return $words; + } + + function _encode(&$string) + { + $string = htmlspecialchars($string); + } + +} diff --git a/includes/pear/Text/Diff/Renderer/unified.php b/includes/pear/Text/Diff/Renderer/unified.php new file mode 100644 index 0000000..f990f72 --- /dev/null +++ b/includes/pear/Text/Diff/Renderer/unified.php @@ -0,0 +1,67 @@ +<?php +/** + * "Unified" diff renderer. + * + * This class renders the diff in classic "unified diff" format. + * + * $Horde: framework/Text_Diff/Diff/Renderer/unified.php,v 1.3.10.7 2009/01/06 15:23:42 jan Exp $ + * + * Copyright 2004-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Ciprian Popovici + * @package Text_Diff + */ + +/** Text_Diff_Renderer */ +require_once 'Text/Diff/Renderer.php'; + +/** + * @package Text_Diff + */ +class Text_Diff_Renderer_unified extends Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + */ + var $_leading_context_lines = 4; + + /** + * Number of trailing context "lines" to preserve. + */ + var $_trailing_context_lines = 4; + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) { + $xbeg .= ',' . $xlen; + } + if ($ylen != 1) { + $ybeg .= ',' . $ylen; + } + return "@@ -$xbeg +$ybeg @@"; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '+'); + } + + function _deleted($lines) + { + return $this->_lines($lines, '-'); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } + +} diff --git a/includes/pear/Text/Diff/ThreeWay.php b/includes/pear/Text/Diff/ThreeWay.php new file mode 100644 index 0000000..5b0357c --- /dev/null +++ b/includes/pear/Text/Diff/ThreeWay.php @@ -0,0 +1,276 @@ +<?php +/** + * A class for computing three way diffs. + * + * $Horde: framework/Text_Diff/Diff/ThreeWay.php,v 1.3.2.4 2009/01/06 15:23:41 jan Exp $ + * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @since 0.3.0 + */ + +/** Text_Diff */ +require_once 'Text/Diff.php'; + +/** + * A class for computing three way diffs. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff_ThreeWay extends Text_Diff { + + /** + * Conflict counter. + * + * @var integer + */ + var $_conflictingBlocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array $orig The original lines to use. + * @param array $final1 The first version to compare to. + * @param array $final2 The second version to compare to. + */ + function Text_Diff_ThreeWay($orig, $final1, $final2) + { + if (extension_loaded('xdiff')) { + $engine = new Text_Diff_Engine_xdiff(); + } else { + $engine = new Text_Diff_Engine_native(); + } + + $this->_edits = $this->_diff3($engine->diff($orig, $final1), + $engine->diff($orig, $final2)); + } + + /** + */ + function mergedOutput($label1 = false, $label2 = false) + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->isConflict()) { + /* FIXME: this should probably be moved somewhere else. */ + $lines = array_merge($lines, + array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), + $edit->final1, + array("======="), + $edit->final2, + array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); + $this->_conflictingBlocks++; + } else { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * @access private + */ + function _diff3($edits1, $edits2) + { + $edits = array(); + $bb = new Text_Diff_ThreeWay_BlockBuilder(); + + $e1 = current($edits1); + $e2 = current($edits2); + while ($e1 || $e2) { + if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { + /* We have copy blocks from both diffs. This is the (only) + * time we want to emit a diff3 copy block. Flush current + * diff3 diff block, if any. */ + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + assert($ncopy > 0); + $edits[] = new Text_Diff_ThreeWay_Op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } else { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } else { + $e2 = next($edits2); + } + } else { + if ($e1 && $e2) { + if ($e1->orig && $e2->orig) { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + + if (is_a($e1, 'Text_Diff_Op_copy')) { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'Text_Diff_Op_copy')) { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) { + $bb->out1($e1->final); + $e1 = next($edits1); + } + if ($e2 && ! $e2->orig) { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + return $edits; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_ThreeWay_Op { + + function Text_Diff_ThreeWay_Op($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) { + if ($this->final1 === $this->final2) { + $this->_merged = &$this->final1; + } elseif ($this->final1 === $this->orig) { + $this->_merged = &$this->final2; + } elseif ($this->final2 === $this->orig) { + $this->_merged = &$this->final1; + } else { + $this->_merged = false; + } + } + + return $this->_merged; + } + + function isConflict() + { + return $this->merged() === false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_ThreeWay_Op_copy extends Text_Diff_ThreeWay_Op { + + function Text_Diff_ThreeWay_Op_Copy($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function isConflict() + { + return false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_ThreeWay_BlockBuilder { + + function Text_Diff_ThreeWay_BlockBuilder() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) { + $this->_append($this->final2, $lines); + } + } + + function isEmpty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->isEmpty()) { + return false; + } else { + $edit = new Text_Diff_ThreeWay_Op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, sizeof($array), 0, $lines); + } + +} diff --git a/includes/pear/Text/Diff3.php b/includes/pear/Text/Diff3.php new file mode 100644 index 0000000..e9aea9f --- /dev/null +++ b/includes/pear/Text/Diff3.php @@ -0,0 +1,276 @@ +<?php +/** + * A class for computing three way diffs. + * + * $Horde: framework/Text_Diff/Diff3.php,v 1.2.10.7 2009/01/06 15:23:41 jan Exp $ + * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @since 0.3.0 + */ + +/** Text_Diff */ +require_once 'Text/Diff.php'; + +/** + * A class for computing three way diffs. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff3 extends Text_Diff { + + /** + * Conflict counter. + * + * @var integer + */ + var $_conflictingBlocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array $orig The original lines to use. + * @param array $final1 The first version to compare to. + * @param array $final2 The second version to compare to. + */ + function Text_Diff3($orig, $final1, $final2) + { + if (extension_loaded('xdiff')) { + $engine = new Text_Diff_Engine_xdiff(); + } else { + $engine = new Text_Diff_Engine_native(); + } + + $this->_edits = $this->_diff3($engine->diff($orig, $final1), + $engine->diff($orig, $final2)); + } + + /** + */ + function mergedOutput($label1 = false, $label2 = false) + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->isConflict()) { + /* FIXME: this should probably be moved somewhere else. */ + $lines = array_merge($lines, + array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), + $edit->final1, + array("======="), + $edit->final2, + array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); + $this->_conflictingBlocks++; + } else { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * @access private + */ + function _diff3($edits1, $edits2) + { + $edits = array(); + $bb = new Text_Diff3_BlockBuilder(); + + $e1 = current($edits1); + $e2 = current($edits2); + while ($e1 || $e2) { + if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { + /* We have copy blocks from both diffs. This is the (only) + * time we want to emit a diff3 copy block. Flush current + * diff3 diff block, if any. */ + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + assert($ncopy > 0); + $edits[] = new Text_Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } else { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } else { + $e2 = next($edits2); + } + } else { + if ($e1 && $e2) { + if ($e1->orig && $e2->orig) { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + + if (is_a($e1, 'Text_Diff_Op_copy')) { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'Text_Diff_Op_copy')) { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) { + $bb->out1($e1->final); + $e1 = next($edits1); + } + if ($e2 && ! $e2->orig) { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + return $edits; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff3_Op { + + function Text_Diff3_Op($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) { + if ($this->final1 === $this->final2) { + $this->_merged = &$this->final1; + } elseif ($this->final1 === $this->orig) { + $this->_merged = &$this->final2; + } elseif ($this->final2 === $this->orig) { + $this->_merged = &$this->final1; + } else { + $this->_merged = false; + } + } + + return $this->_merged; + } + + function isConflict() + { + return $this->merged() === false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff3_Op_copy extends Text_Diff3_Op { + + function Text_Diff3_Op_Copy($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function isConflict() + { + return false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff3_BlockBuilder { + + function Text_Diff3_BlockBuilder() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) { + $this->_append($this->final2, $lines); + } + } + + function isEmpty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->isEmpty()) { + return false; + } else { + $edit = new Text_Diff3_Op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, sizeof($array), 0, $lines); + } + +} diff --git a/includes/pear/Text/Wiki.php b/includes/pear/Text/Wiki.php new file mode 100644 index 0000000..27a9161 --- /dev/null +++ b/includes/pear/Text/Wiki.php @@ -0,0 +1,1550 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Parse structured wiki text and render into arbitrary formats such as XHTML. + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Wiki.php 248433 2007-12-17 16:03:48Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * The baseline abstract parser class. + */ +require_once 'Text/Wiki/Parse.php'; + +/** + * The baseline abstract render class. + */ +require_once 'Text/Wiki/Render.php'; + +/** + * Parse structured wiki text and render into arbitrary formats such as XHTML. + * + * This is the "master" class for handling the management and convenience + * functions to transform Wiki-formatted text. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: 1.2.1 + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki { + + /** + * + * The default list of rules, in order, to apply to the source text. + * + * @access public + * + * @var array + * + */ + + var $rules = array( + 'Prefilter', + 'Delimiter', + 'Code', + 'Function', + 'Html', + 'Raw', + 'Include', + 'Embed', + 'Anchor', + 'Heading', + 'Toc', + 'Horiz', + 'Break', + 'Blockquote', + 'List', + 'Deflist', + 'Table', + 'Image', + 'Phplookup', + 'Center', + 'Newline', + 'Paragraph', + 'Url', + 'Freelink', + 'Interwiki', + 'Wikilink', + 'Colortext', + 'Strong', + 'Bold', + 'Emphasis', + 'Italic', + 'Underline', + 'Tt', + 'Superscript', + 'Subscript', + 'Revise', + 'Tighten' + ); + + + /** + * + * The list of rules to not-apply to the source text. + * + * @access public + * + * @var array + * + */ + + var $disable = array( + 'Html', + 'Include', + 'Embed' + ); + + + /** + * + * Custom configuration for rules at the parsing stage. + * + * In this array, the key is the parsing rule name, and the value is + * an array of key-value configuration pairs corresponding to the $conf + * property in the target parsing rule. + * + * For example: + * + * <code> + * $parseConf = array( + * 'Include' => array( + * 'base' => '/path/to/scripts/' + * ) + * ); + * </code> + * + * Note that most default rules do not need any parsing configuration. + * + * @access public + * + * @var array + * + */ + + var $parseConf = array(); + + + /** + * + * Custom configuration for rules at the rendering stage. + * + * Because rendering may be different for each target format, the + * first-level element in this array is always a format name (e.g., + * 'Xhtml'). + * + * Within that first level element, the subsequent elements match the + * $parseConf format. That is, the sub-key is the rendering rule name, + * and the sub-value is an array of key-value configuration pairs + * corresponding to the $conf property in the target rendering rule. + * + * @access public + * + * @var array + * + */ + + var $renderConf = array( + 'Docbook' => array(), + 'Latex' => array(), + 'Pdf' => array(), + 'Plain' => array(), + 'Rtf' => array(), + 'Xhtml' => array() + ); + + + /** + * + * Custom configuration for the output format itself. + * + * Even though Text_Wiki will render the tokens from parsed text, + * the format itself may require some configuration. For example, + * RTF needs to know font names and sizes, PDF requires page layout + * information, and DocBook needs a section hierarchy. This array + * matches the $conf property of the the format-level renderer + * (e.g., Text_Wiki_Render_Xhtml). + * + * In this array, the key is the rendering format name, and the value is + * an array of key-value configuration pairs corresponding to the $conf + * property in the rendering format rule. + * + * @access public + * + * @var array + * + */ + + var $formatConf = array( + 'Docbook' => array(), + 'Latex' => array(), + 'Pdf' => array(), + 'Plain' => array(), + 'Rtf' => array(), + 'Xhtml' => array() + ); + + + /** + * + * The delimiter for token numbers of parsed elements in source text. + * + * @access public + * + * @var string + * + */ + + var $delim = "\31"; + + + /** + * + * The tokens generated by rules as the source text is parsed. + * + * As Text_Wiki applies rule classes to the source text, it will + * replace portions of the text with a delimited token number. This + * is the array of those tokens, representing the replaced text and + * any options set by the parser for that replaced text. + * + * The tokens array is sequential; each element is itself a sequential + * array where element 0 is the name of the rule that generated the + * token, and element 1 is an associative array where the key is an + * option name and the value is an option value. + * + * @access private + * + * @var array + * + */ + + var $tokens = array(); + + /** + * How many tokens generated pro rules. + * + * Intended to load only necessary render objects + * + * @access private + * @var array + */ + var $_countRulesTokens = array(); + + + /** + * + * The source text to which rules will be applied. + * + * This text will be transformed in-place, which means that it will + * change as the rules are applied. + * + * @access private + * + * @var string + * + */ + + var $source = ''; + + /** + * The output text + * + * @var string + */ + var $output = ''; + + + /** + * + * Array of rule parsers. + * + * Text_Wiki creates one instance of every rule that is applied to + * the source text; this array holds those instances. The array key + * is the rule name, and the array value is an instance of the rule + * class. + * + * @access private + * + * @var array + * + */ + + var $parseObj = array(); + + + /** + * + * Array of rule renderers. + * + * Text_Wiki creates one instance of every rule that is applied to + * the source text; this array holds those instances. The array key + * is the rule name, and the array value is an instance of the rule + * class. + * + * @access private + * + * @var array + * + */ + + var $renderObj = array(); + + + /** + * + * Array of format renderers. + * + * @access private + * + * @var array + * + */ + + var $formatObj = array(); + + + /** + * + * Array of paths to search, in order, for parsing and rendering rules. + * + * @access private + * + * @var array + * + */ + + var $path = array( + 'parse' => array(), + 'render' => array() + ); + + + + /** + * + * The directory separator character. + * + * @access private + * + * @var string + * + */ + + var $_dirSep = DIRECTORY_SEPARATOR; + + /** + * Temporary configuration variable + * + * @var string + */ + var $renderingType = 'normal'; + + /** + * Stack of rendering callbacks + * + * @var Array + */ + var $_renderCallbacks = array(); + + /** + * Current output block + * + * @var string + */ + var $_block; + + /** + * A stack of blocks + * + * @param Array + */ + var $_blocks; + + /** + * + * Constructor. + * + * **DEPRECATED** + * Please use the singleton() or factory() methods. + * + * @access public + * + * @param array $rules The set of rules to load for this object. Defaults + * to null, which will load the default ruleset for this parser. + */ + + function Text_Wiki($rules = null) + { + if (is_array($rules)) { + $this->rules = array(); + foreach ($rules as $rule) { + $this->rules[] = ucfirst($rule); + } + } + + $this->addPath( + 'parse', + $this->fixPath(dirname(__FILE__)) . 'Wiki/Parse/Default/' + ); + $this->addPath( + 'render', + $this->fixPath(dirname(__FILE__)) . 'Wiki/Render/' + ); + + } + + /** + * Singleton. + * + * This avoids instantiating multiple Text_Wiki instances where a number + * of objects are required in one call, e.g. to save memory in a + * CMS invironment where several parsers are required in a single page. + * + * $single = & singleton(); + * + * or + * + * $single = & singleton('Parser', array('Prefilter', 'Delimiter', 'Code', 'Function', + * 'Html', 'Raw', 'Include', 'Embed', 'Anchor', 'Heading', 'Toc', 'Horiz', + * 'Break', 'Blockquote', 'List', 'Deflist', 'Table', 'Image', 'Phplookup', + * 'Center', 'Newline', 'Paragraph', 'Url', 'Freelink', 'Interwiki', 'Wikilink', + * 'Colortext', 'Strong', 'Bold', 'Emphasis', 'Italic', 'Underline', 'Tt', + * 'Superscript', 'Subscript', 'Revise', 'Tighten')); + * + * Call using a subset of this list. The order of passing rulesets in the + * $rules array is important! + * + * After calling this, call $single->setParseConf(), setRenderConf() or setFormatConf() + * as usual for a constructed object of this class. + * + * The internal static array of singleton objects has no index on the parser + * rules, the only index is on the parser name. So if you call this multiple + * times with different rules but the same parser name, you will get the same + * static parser object each time. + * + * @access public + * @static + * @since Method available since Release 1.1.0 + * @param string $parser The parser to be used (defaults to 'Default'). + * @param array $rules The set of rules to instantiate the object. This + * will only be used when the first call to singleton is made, if included + * in further calls it will be effectively ignored. + * @return &object a reference to the Text_Wiki unique instantiation. + */ + function &singleton($parser = 'Default', $rules = null) + { + static $only = array(); + if (!isset($only[$parser])) { + $ret = & Text_Wiki::factory($parser, $rules); + if (Text_Wiki::isError($ret)) { + return $ret; + } + $only[$parser] =& $ret; + } + return $only[$parser]; + } + + /** + * Returns a Text_Wiki Parser class for the specified parser. + * + * @access public + * @static + * @param string $parser The name of the parse to instantiate + * you need to have Text_Wiki_XXX installed to use $parser = 'XXX', it's E_FATAL + * @param array $rules The rules to pass into the constructor + * {@see Text_Wiki::singleton} for a list of rules + * @return Text_Wiki a Parser object extended from Text_Wiki + */ + function &factory($parser = 'Default', $rules = null) + { + $class = 'Text_Wiki_' . $parser; + $file = str_replace('_', '/', $class).'.php'; + if (!class_exists($class)) { + require_once $file; + if (!class_exists($class)) { + return Text_Wiki::error( + 'Class ' . $class . ' does not exist after requiring '. $file . + ', install package ' . $class . "\n"); + } + } + + $obj =& new $class($rules); + return $obj; + } + + /** + * + * Set parser configuration for a specific rule and key. + * + * @access public + * + * @param string $rule The parse rule to set config for. + * + * @param array|string $arg1 The full config array to use for the + * parse rule, or a conf key in that array. + * + * @param string $arg2 The config value for the key. + * + * @return void + * + */ + + function setParseConf($rule, $arg1, $arg2 = null) + { + $rule = ucwords(strtolower($rule)); + + if (! isset($this->parseConf[$rule])) { + $this->parseConf[$rule] = array(); + } + + // if first arg is an array, use it as the entire + // conf array for the rule. otherwise, treat arg1 + // as a key and arg2 as a value for the rule conf. + if (is_array($arg1)) { + $this->parseConf[$rule] = $arg1; + } else { + $this->parseConf[$rule][$arg1] = $arg2; + } + } + + + /** + * + * Get parser configuration for a specific rule and key. + * + * @access public + * + * @param string $rule The parse rule to get config for. + * + * @param string $key A key in the conf array; if null, + * returns the entire conf array. + * + * @return mixed The whole conf array if no key is specified, + * or the specific conf key value. + * + */ + + function getParseConf($rule, $key = null) + { + $rule = ucwords(strtolower($rule)); + + // the rule does not exist + if (! isset($this->parseConf[$rule])) { + return null; + } + + // no key requested, return the whole array + if (is_null($key)) { + return $this->parseConf[$rule]; + } + + // does the requested key exist? + if (isset($this->parseConf[$rule][$key])) { + // yes, return that value + return $this->parseConf[$rule][$key]; + } else { + // no + return null; + } + } + + + /** + * + * Set renderer configuration for a specific format, rule, and key. + * + * @access public + * + * @param string $format The render format to set config for. + * + * @param string $rule The render rule to set config for in the format. + * + * @param array|string $arg1 The config array, or the config key + * within the render rule. + * + * @param string $arg2 The config value for the key. + * + * @return void + * + */ + + function setRenderConf($format, $rule, $arg1, $arg2 = null) + { + $format = ucwords(strtolower($format)); + $rule = ucwords(strtolower($rule)); + + if (! isset($this->renderConf[$format])) { + $this->renderConf[$format] = array(); + } + + if (! isset($this->renderConf[$format][$rule])) { + $this->renderConf[$format][$rule] = array(); + } + + // if first arg is an array, use it as the entire + // conf array for the render rule. otherwise, treat arg1 + // as a key and arg2 as a value for the render rule conf. + if (is_array($arg1)) { + $this->renderConf[$format][$rule] = $arg1; + } else { + $this->renderConf[$format][$rule][$arg1] = $arg2; + } + } + + + /** + * + * Get renderer configuration for a specific format, rule, and key. + * + * @access public + * + * @param string $format The render format to get config for. + * + * @param string $rule The render format rule to get config for. + * + * @param string $key A key in the conf array; if null, + * returns the entire conf array. + * + * @return mixed The whole conf array if no key is specified, + * or the specific conf key value. + * + */ + + function getRenderConf($format, $rule, $key = null) + { + $format = ucwords(strtolower($format)); + $rule = ucwords(strtolower($rule)); + + if (! isset($this->renderConf[$format]) || + ! isset($this->renderConf[$format][$rule])) { + return null; + } + + // no key requested, return the whole array + if (is_null($key)) { + return $this->renderConf[$format][$rule]; + } + + // does the requested key exist? + if (isset($this->renderConf[$format][$rule][$key])) { + // yes, return that value + return $this->renderConf[$format][$rule][$key]; + } else { + // no + return null; + } + + } + + /** + * + * Set format configuration for a specific rule and key. + * + * @access public + * + * @param string $format The format to set config for. + * + * @param string $key The config key within the format. + * + * @param string $val The config value for the key. + * + * @return void + * + */ + + function setFormatConf($format, $arg1, $arg2 = null) + { + if (! is_array($this->formatConf[$format])) { + $this->formatConf[$format] = array(); + } + + // if first arg is an array, use it as the entire + // conf array for the format. otherwise, treat arg1 + // as a key and arg2 as a value for the format conf. + if (is_array($arg1)) { + $this->formatConf[$format] = $arg1; + } else { + $this->formatConf[$format][$arg1] = $arg2; + } + } + + + + /** + * + * Get configuration for a specific format and key. + * + * @access public + * + * @param string $format The format to get config for. + * + * @param mixed $key A key in the conf array; if null, + * returns the entire conf array. + * + * @return mixed The whole conf array if no key is specified, + * or the specific conf key value. + * + */ + + function getFormatConf($format, $key = null) + { + // the format does not exist + if (! isset($this->formatConf[$format])) { + return null; + } + + // no key requested, return the whole array + if (is_null($key)) { + return $this->formatConf[$format]; + } + + // does the requested key exist? + if (isset($this->formatConf[$format][$key])) { + // yes, return that value + return $this->formatConf[$format][$key]; + } else { + // no + return null; + } + } + + + /** + * + * Inserts a rule into to the rule set. + * + * @access public + * + * @param string $name The name of the rule. Should be different from + * all other keys in the rule set. + * + * @param string $tgt The rule after which to insert this new rule. By + * default (null) the rule is inserted at the end; if set to '', inserts + * at the beginning. + * + * @return void + * + */ + + function insertRule($name, $tgt = null) + { + $name = ucwords(strtolower($name)); + if (! is_null($tgt)) { + $tgt = ucwords(strtolower($tgt)); + } + + // does the rule name to be inserted already exist? + if (in_array($name, $this->rules)) { + // yes, return + return null; + } + + // the target name is not null, and not '', but does not exist + // in the list of rules. this means we're trying to insert after + // a target key, but the target key isn't there. + if (! is_null($tgt) && $tgt != '' && + ! in_array($tgt, $this->rules)) { + return false; + } + + // if $tgt is null, insert at the end. We know this is at the + // end (instead of resetting an existing rule) becuase we exited + // at the top of this method if the rule was already in place. + if (is_null($tgt)) { + $this->rules[] = $name; + return true; + } + + // save a copy of the current rules, then reset the rule set + // so we can insert in the proper place later. + // where to insert the rule? + if ($tgt == '') { + // insert at the beginning + array_unshift($this->rules, $name); + return true; + } + + // insert after the named rule + $tmp = $this->rules; + $this->rules = array(); + + foreach ($tmp as $val) { + $this->rules[] = $val; + if ($val == $tgt) { + $this->rules[] = $name; + } + } + + return true; + + } + + + /** + * + * Delete (remove or unset) a rule from the $rules property. + * + * @access public + * + * @param string $rule The name of the rule to remove. + * + * @return void + * + */ + + function deleteRule($name) + { + $name = ucwords(strtolower($name)); + $key = array_search($name, $this->rules); + if ($key !== false) { + unset($this->rules[$key]); + } + } + + + /** + * + * Change from one rule to another in-place. + * + * @access public + * + * @param string $old The name of the rule to change from. + * + * @param string $new The name of the rule to change to. + * + * @return void + * + */ + + function changeRule($old, $new) + { + $old = ucwords(strtolower($old)); + $new = ucwords(strtolower($new)); + $key = array_search($old, $this->rules); + if ($key !== false) { + // delete the new name , case it was already there + $this->deleteRule($new); + $this->rules[$key] = $new; + } + } + + + /** + * + * Enables a rule so that it is applied when parsing. + * + * @access public + * + * @param string $rule The name of the rule to enable. + * + * @return void + * + */ + + function enableRule($name) + { + $name = ucwords(strtolower($name)); + $key = array_search($name, $this->disable); + if ($key !== false) { + unset($this->disable[$key]); + } + } + + + /** + * + * Disables a rule so that it is not applied when parsing. + * + * @access public + * + * @param string $rule The name of the rule to disable. + * + * @return void + * + */ + + function disableRule($name) + { + $name = ucwords(strtolower($name)); + $key = array_search($name, $this->disable); + if ($key === false) { + $this->disable[] = $name; + } + } + + + /** + * + * Parses and renders the text passed to it, and returns the results. + * + * First, the method parses the source text, applying rules to the + * text as it goes. These rules will modify the source text + * in-place, replacing some text with delimited tokens (and + * populating the $this->tokens array as it goes). + * + * Next, the method renders the in-place tokens into the requested + * output format. + * + * Finally, the method returns the transformed text. Note that the + * source text is transformed in place; once it is transformed, it is + * no longer the same as the original source text. + * + * @access public + * + * @param string $text The source text to which wiki rules should be + * applied, both for parsing and for rendering. + * + * @param string $format The target output format, typically 'xhtml'. + * If a rule does not support a given format, the output from that + * rule is rule-specific. + * + * @return string The transformed wiki text. + * + */ + + function transform($text, $format = 'Xhtml') + { + $this->parse($text); + return $this->render($format); + } + + + /** + * + * Sets the $_source text property, then parses it in place and + * retains tokens in the $_tokens array property. + * + * @access public + * + * @param string $text The source text to which wiki rules should be + * applied, both for parsing and for rendering. + * + * @return void + * + */ + + function parse($text) + { + // set the object property for the source text + $this->source = $text; + + // reset the tokens. + $this->tokens = array(); + $this->_countRulesTokens = array(); + + // apply the parse() method of each requested rule to the source + // text. + foreach ($this->rules as $name) { + // do not parse the rules listed in $disable + if (! in_array($name, $this->disable)) { + + // load the parsing object + $this->loadParseObj($name); + + // load may have failed; only parse if + // an object is in the array now + if (is_object($this->parseObj[$name])) { + $this->parseObj[$name]->parse(); + } + } + } + } + + + /** + * + * Renders tokens back into the source text, based on the requested format. + * + * @access public + * + * @param string $format The target output format, typically 'xhtml'. + * If a rule does not support a given format, the output from that + * rule is rule-specific. + * + * @return string The transformed wiki text. + * + */ + + function render($format = 'Xhtml') + { + // the rendering method we're going to use from each rule + $format = ucwords(strtolower($format)); + + // the eventual output text + $this->output = ''; + + // when passing through the parsed source text, keep track of when + // we are in a delimited section + $in_delim = false; + + // when in a delimited section, capture the token key number + $key = ''; + + // load the format object, or crap out if we can't find it + $result = $this->loadFormatObj($format); + if ($this->isError($result)) { + return $result; + } + + // pre-rendering activity + if (is_object($this->formatObj[$format])) { + $this->output .= $this->formatObj[$format]->pre(); + } + + // load the render objects + foreach (array_keys($this->_countRulesTokens) as $rule) { + $this->loadRenderObj($format, $rule); + } + + if ($this->renderingType == 'preg') { + $this->output = preg_replace_callback('/'.$this->delim.'(\d+)'.$this->delim.'/', + array(&$this, '_renderToken'), + $this->source); + /* +//Damn strtok()! Why does it "skip" empty parts of the string. It's useless now! + } elseif ($this->renderingType == 'strtok') { + echo '<pre>'.htmlentities($this->source).'</pre>'; + $t = strtok($this->source, $this->delim); + $inToken = true; + $i = 0; + while ($t !== false) { + echo 'Token: '.$i.'<pre>"'.htmlentities($t).'"</pre><br/><br/>'; + if ($inToken) { + //$this->output .= $this->renderObj[$this->tokens[$t][0]]->token($this->tokens[$t][1]); + } else { + $this->output .= $t; + } + $inToken = !$inToken; + $t = strtok($this->delim); + ++$i; + } + */ + } else { + // pass through the parsed source text character by character + $this->_block = ''; + $tokenStack = array(); + $k = strlen($this->source); + for ($i = 0; $i < $k; $i++) { + + // the current character + $char = $this->source{$i}; + + // are alredy in a delimited section? + if ($in_delim) { + + // yes; are we ending the section? + if ($char == $this->delim) { + + if (count($this->_renderCallbacks) == 0) { + $this->output .= $this->_block; + $this->_block = ''; + } + if (isset($opts['type'])) { + if ($opts['type'] == 'start') { + array_push($tokenStack, $rule); + } elseif ($opts['type'] == 'end') { + if ($tokenStack[count($tokenStack) - 1] != $rule) { + return Text_Wiki::error('Unbalanced tokens, check your syntax'); + } else { + array_pop($tokenStack); + } + } + } + + // yes, get the replacement text for the delimited + // token number and unset the flag. + $key = (int)$key; + $rule = $this->tokens[$key][0]; + $opts = $this->tokens[$key][1]; + $this->_block .= $this->renderObj[$rule]->token($opts); + $in_delim = false; + + } else { + + // no, add to the delimited token key number + $key .= $char; + + } + + } else { + + // not currently in a delimited section. + // are we starting into a delimited section? + if ($char == $this->delim) { + // yes, reset the previous key and + // set the flag. + $key = ''; + $in_delim = true; + + } else { + // no, add to the output as-is + $this->_block .= $char; + } + } + } + } + + if (count($this->_renderCallbacks)) { + return $this->error('Render callbacks left over after processing finished'); + } + /* + while (count($this->_renderCallbacks)) { + $this->popRenderCallback(); + } + */ + if (strlen($this->_block)) { + $this->output .= $this->_block; + $this->_block = ''; + } + + // post-rendering activity + if (is_object($this->formatObj[$format])) { + $this->output .= $this->formatObj[$format]->post(); + } + + // return the rendered source text. + return $this->output; + } + + /** + * Renders a token, for use only as an internal callback + * + * @param array Matches from preg_rpelace_callback, [1] is the token number + * @return string The rendered text for the token + * @access private + */ + function _renderToken($matches) { + return $this->renderObj[$this->tokens[$matches[1]][0]]->token($this->tokens[$matches[1]][1]); + } + + function registerRenderCallback($callback) { + $this->_blocks[] = $this->_block; + $this->_block = ''; + $this->_renderCallbacks[] = $callback; + } + + function popRenderCallback() { + if (count($this->_renderCallbacks) == 0) { + return Text_Wiki::error('Render callback popped when no render callbacks in stack'); + } else { + $callback = array_pop($this->_renderCallbacks); + $this->_block = call_user_func($callback, $this->_block); + if (count($this->_blocks)) { + $parentBlock = array_pop($this->_blocks); + $this->_block = $parentBlock.$this->_block; + } + if (count($this->_renderCallbacks) == 0) { + $this->output .= $this->_block; + $this->_block = ''; + } + } + } + + /** + * + * Returns the parsed source text with delimited token placeholders. + * + * @access public + * + * @return string The parsed source text. + * + */ + + function getSource() + { + return $this->source; + } + + + /** + * + * Returns tokens that have been parsed out of the source text. + * + * @access public + * + * @param array $rules If an array of rule names is passed, only return + * tokens matching these rule names. If no array is passed, return all + * tokens. + * + * @return array An array of tokens. + * + */ + + function getTokens($rules = null) + { + if (is_null($rules)) { + return $this->tokens; + } else { + settype($rules, 'array'); + $result = array(); + foreach ($this->tokens as $key => $val) { + if (in_array($val[0], $rules)) { + $result[$key] = $val; + } + } + return $result; + } + } + + + /** + * + * Add a token to the Text_Wiki tokens array, and return a delimited + * token number. + * + * @access public + * + * @param array $options An associative array of options for the new + * token array element. The keys and values are specific to the + * rule, and may or may not be common to other rule options. Typical + * options keys are 'text' and 'type' but may include others. + * + * @param boolean $id_only If true, return only the token number, not + * a delimited token string. + * + * @return string|int By default, return the number of the + * newly-created token array element with a delimiter prefix and + * suffix; however, if $id_only is set to true, return only the token + * number (no delimiters). + * + */ + + function addToken($rule, $options = array(), $id_only = false) + { + // increment the token ID number. note that if you parse + // multiple times with the same Text_Wiki object, the ID number + // will not reset to zero. + static $id; + if (! isset($id)) { + $id = 0; + } else { + $id ++; + } + + // force the options to be an array + settype($options, 'array'); + + // add the token + $this->tokens[$id] = array( + 0 => $rule, + 1 => $options + ); + if (!isset($this->_countRulesTokens[$rule])) { + $this->_countRulesTokens[$rule] = 1; + } else { + ++$this->_countRulesTokens[$rule]; + } + + // return a value + if ($id_only) { + // return the last token number + return $id; + } else { + // return the token number with delimiters + return $this->delim . $id . $this->delim; + } + } + + + /** + * + * Set or re-set a token with specific information, overwriting any + * previous rule name and rule options. + * + * @access public + * + * @param int $id The token number to reset. + * + * @param int $rule The rule name to use. + * + * @param array $options An associative array of options for the + * token array element. The keys and values are specific to the + * rule, and may or may not be common to other rule options. Typical + * options keys are 'text' and 'type' but may include others. + * + * @return void + * + */ + + function setToken($id, $rule, $options = array()) + { + $oldRule = $this->tokens[$id][0]; + // reset the token + $this->tokens[$id] = array( + 0 => $rule, + 1 => $options + ); + if ($rule != $oldRule) { + if (!($this->_countRulesTokens[$oldRule]--)) { + unset($this->_countRulesTokens[$oldRule]); + } + if (!isset($this->_countRulesTokens[$rule])) { + $this->_countRulesTokens[$rule] = 1; + } else { + ++$this->_countRulesTokens[$rule]; + } + } + } + + + /** + * + * Load a rule parser class file. + * + * @access public + * + * @return bool True if loaded, false if not. + * + */ + + function loadParseObj($rule) + { + $rule = ucwords(strtolower($rule)); + $file = $rule . '.php'; + $class = "Text_Wiki_Parse_$rule"; + + if (! class_exists($class)) { + $loc = $this->findFile('parse', $file); + if ($loc) { + // found the class + include_once $loc; + } else { + // can't find the class + $this->parseObj[$rule] = null; + // can't find the class + return $this->error( + "Parse rule '$rule' not found" + ); + } + } + + $this->parseObj[$rule] =& new $class($this); + + } + + + /** + * + * Load a rule-render class file. + * + * @access public + * + * @return bool True if loaded, false if not. + * + */ + + function loadRenderObj($format, $rule) + { + $format = ucwords(strtolower($format)); + $rule = ucwords(strtolower($rule)); + $file = "$format/$rule.php"; + $class = "Text_Wiki_Render_$format" . "_$rule"; + + if (! class_exists($class)) { + // load the class + $loc = $this->findFile('render', $file); + if ($loc) { + // found the class + include_once $loc; + } else { + // can't find the class + return $this->error( + "Render rule '$rule' in format '$format' not found" + ); + } + } + + $this->renderObj[$rule] =& new $class($this); + } + + + /** + * + * Load a format-render class file. + * + * @access public + * + * @return bool True if loaded, false if not. + * + */ + + function loadFormatObj($format) + { + $format = ucwords(strtolower($format)); + $file = $format . '.php'; + $class = "Text_Wiki_Render_$format"; + + if (! class_exists($class)) { + $loc = $this->findFile('render', $file); + if ($loc) { + // found the class + include_once $loc; + } else { + // can't find the class + return $this->error( + "Rendering format class '$class' not found" + ); + } + } + + $this->formatObj[$format] =& new $class($this); + } + + + /** + * + * Add a path to a path array. + * + * @access public + * + * @param string $type The path-type to add (parse or render). + * + * @param string $dir The directory to add to the path-type. + * + * @return void + * + */ + + function addPath($type, $dir) + { + $dir = $this->fixPath($dir); + if (! isset($this->path[$type])) { + $this->path[$type] = array($dir); + } else { + array_unshift($this->path[$type], $dir); + } + } + + + /** + * + * Get the current path array for a path-type. + * + * @access public + * + * @param string $type The path-type to look up (plugin, filter, or + * template). If not set, returns all path types. + * + * @return array The array of paths for the requested type. + * + */ + + function getPath($type = null) + { + if (is_null($type)) { + return $this->path; + } elseif (! isset($this->path[$type])) { + return array(); + } else { + return $this->path[$type]; + } + } + + + /** + * + * Searches a series of paths for a given file. + * + * @param array $type The type of paths to search (template, plugin, + * or filter). + * + * @param string $file The file name to look for. + * + * @return string|bool The full path and file name for the target file, + * or boolean false if the file is not found in any of the paths. + * + */ + + function findFile($type, $file) + { + // get the set of paths + $set = $this->getPath($type); + + // start looping through them + foreach ($set as $path) { + $fullname = $path . $file; + if (file_exists($fullname) && is_readable($fullname)) { + return $fullname; + } + } + + // could not find the file in the set of paths + return false; + } + + + /** + * + * Append a trailing '/' to paths, unless the path is empty. + * + * @access private + * + * @param string $path The file path to fix + * + * @return string The fixed file path + * + */ + + function fixPath($path) + { + $len = strlen($this->_dirSep); + + if (! empty($path) && + substr($path, -1 * $len, $len) != $this->_dirSep) { + return $path . $this->_dirSep; + } else { + return $path; + } + } + + + /** + * + * Simple error-object generator. + * + * @access public + * + * @param string $message The error message. + * + * @return object PEAR_Error + * + */ + + function &error($message) + { + if (! class_exists('PEAR_Error')) { + include_once 'PEAR.php'; + } + return PEAR::throwError($message); + } + + + /** + * + * Simple error checker. + * + * @access public + * + * @param mixed $obj Check if this is a PEAR_Error object or not. + * + * @return bool True if a PEAR_Error, false if not. + * + */ + + function isError(&$obj) + { + return is_a($obj, 'PEAR_Error'); + } +} + +?> diff --git a/includes/pear/Text/Wiki/Creole.php b/includes/pear/Text/Wiki/Creole.php new file mode 100644 index 0000000..255eaac --- /dev/null +++ b/includes/pear/Text/Wiki/Creole.php @@ -0,0 +1,150 @@ +<?php + +/** + * + * Parse structured wiki text and render into arbitrary formats such as XHTML. + * This is the Text_Wiki extension for Mediawiki markup + * + * PHP versions 4 and 5 + * + * @category Text + * + * @package Text_Wiki + * + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * + * @link http://pear.php.net/package/Text_Wiki + * + * @version CVS: $Id: Creole.php 274202 2009-01-22 13:28:32Z mic $ + * + */ + +/** + * + * "Master" class for handling the management and convenience + * + */ + +require_once 'Text/Wiki.php'; + +/** + * + * Base Text_Wiki handler class extension for Creole markup + * + * @category Text + * + * @package Text_Wiki + * + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * + * @link http://pear.php.net/package/Text_Wiki + * + * @see Text_Wiki::Text_Wiki() + * + */ + +class Text_Wiki_Creole extends Text_Wiki { + + // *single newlines* are handled as in most wikis (ignored) + // if Newline is removed from rules, they will be handled as in word-processors (meaning a paragraph break) + + var $rules = array( + 'Prefilter', + 'Delimiter', + 'Preformatted', + 'Tt', + 'Trim', + 'Break', + 'Raw', + 'Box', + 'Footnote', + 'Heading', + 'Newline', + 'Deflist', + 'Blockquote', + 'Newline', + 'Url', + 'Wikilink', + 'Image', + //'Heading', + 'Table', + 'Center', + 'Horiz', + 'Deflist', + 'List', + 'Address', + 'Paragraph', + 'Superscript', + 'Subscript', + 'Underline', + 'Emphasis', + 'Strong', + //'Italic', + //'Bold', + 'Tighten' + ); + + /** + * Constructor: just adds the path to Creole rules + * + * @access public + * @param array $rules The set of rules to load for this object. + */ + + function Text_Wiki_Creole($rules = null) { + parent::Text_Wiki($rules); + $this->addPath('parse', $this->fixPath(dirname(__FILE__)).'Parse/Creole'); + $this->renderingType = 'char'; + $this->setRenderConf('xhtml', 'center', 'css', 'center'); + $this->setRenderConf('xhtml', 'url', 'target', null); + } + + function checkInnerTags(&$text) { + $started = array(); + $i = false; + while (($i = strpos($text, $this->delim, $i)) !== false) { + $j = strpos($text, $this->delim, $i + 1); + $t = substr($text, $i + 1, $j - $i - 1); + $i = $j + 1; + $rule = strtolower($this->tokens[$t][0]); + $type = $this->tokens[$t][1]['type']; + + if ($type == 'start') { + if (empty($started[$rule])) { + $started[$rule] = 0; + } + $started[$rule] += 1; + } + else if ($type == 'end') { + if (empty($started[$rule])) return false; + + $started[$rule] -= 1; + if (! $started[$rule]) unset($started[$rule]); + } + } + return ! (count($started) > 0); + } + + function restoreRaw($text) { + $i = false; + while (($i = strpos($text, $this->delim, $i)) !== false) { + $j = strpos($text, $this->delim, $i + 1); + $t = substr($text, $i + 1, $j - $i - 1); + $rule = strtolower($this->tokens[$t][0]); + + if ($rule == 'raw') { + $text = str_replace($this->delim. $t. $this->delim, $this->tokens[$t][1]['text'], $text); + } + else { + $i = $j + 1; + } + } + return $text; + } +} + +?> diff --git a/includes/pear/Text/Wiki/Default.php b/includes/pear/Text/Wiki/Default.php new file mode 100644 index 0000000..379cf12 --- /dev/null +++ b/includes/pear/Text/Wiki/Default.php @@ -0,0 +1,27 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Parse structured wiki text and render into arbitrary formats such as XHTML. + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Justin Patrin <justinpatrin@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Default.php 208363 2006-03-01 16:58:17Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +require_once('Text/Wiki.php'); + +/** + * This is the parser for the Default ruleset. For now, this simply extends Text_Wiki. + * + * @category Text + * @package Text_Wiki + * @version Release: @package_version@ + * @author Justin Patrin <justinpatrin@php.net> + */ +class Text_Wiki_Default extends Text_Wiki { +} diff --git a/includes/pear/Text/Wiki/Parse.php b/includes/pear/Text/Wiki/Parse.php new file mode 100644 index 0000000..1b14c89 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse.php @@ -0,0 +1,264 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Baseline rule class for extension into a "real" parser component. + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Parse.php 191781 2005-07-29 08:57:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Baseline rule class for extension into a "real" parser component. + * + * Text_Wiki_Rule classes do not stand on their own; they are called by a + * Text_Wiki object, typcially in the transform() method. Each rule class + * performs three main activities: parse, process, and render. + * + * The parse() method takes a regex and applies it to the whole block of + * source text at one time. Each match is sent as $matches to the + * process() method. + * + * The process() method acts on the matched text from the source, and + * then processes the source text is some way. This may mean the + * creation of a delimited token using addToken(). In every case, the + * process() method returns the text that should replace the matched text + * from parse(). + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Parse { + + + /** + * + * Configuration options for this parser rule. + * + * @access public + * + * @var string + * + */ + + var $conf = array(); + + + /** + * + * Regular expression to find matching text for this rule. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = null; + + + /** + * + * The name of this rule for new token array elements. + * + * @access public + * + * @var string + * + */ + + var $rule = null; + + + /** + * + * A reference to the calling Text_Wiki object. + * + * This is needed so that each rule has access to the same source + * text, token set, URLs, interwiki maps, page names, etc. + * + * @access public + * + * @var object + */ + + var $wiki = null; + + + /** + * + * Constructor for this parser rule. + * + * @access public + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + */ + + function Text_Wiki_Parse(&$obj) + { + // set the reference to the calling Text_Wiki object; + // this allows us access to the shared source text, token + // array, etc. + $this->wiki =& $obj; + + // set the name of this rule; generally used when adding + // to the tokens array. strip off the Text_Wiki_Parse_ portion. + // text_wiki_parse_ + // 0123456789012345 + $tmp = substr(get_class($this), 16); + $this->rule = ucwords(strtolower($tmp)); + + // override config options for the rule if specified + if (isset($this->wiki->parseConf[$this->rule]) && + is_array($this->wiki->parseConf[$this->rule])) { + + $this->conf = array_merge( + $this->conf, + $this->wiki->parseConf[$this->rule] + ); + + } + } + + + /** + * + * Abstrct method to parse source text for matches. + * + * Applies the rule's regular expression to the source text, passes + * every match to the process() method, and replaces the matched text + * with the results of the processing. + * + * @access public + * + * @see Text_Wiki_Parse::process() + * + */ + + function parse() + { + $this->wiki->source = preg_replace_callback( + $this->regex, + array(&$this, 'process'), + $this->wiki->source + ); + } + + + /** + * + * Abstract method to generate replacements for matched text. + * + * @access public + * + * @param array $matches An array of matches from the parse() method + * as generated by preg_replace_callback. $matches[0] is the full + * matched string, $matches[1] is the first matched pattern, + * $matches[2] is the second matched pattern, and so on. + * + * @return string The processed text replacement; defaults to the + * full matched string (i.e., no changes to the text). + * + * @see Text_Wiki_Parse::parse() + * + */ + + function process(&$matches) + { + return $matches[0]; + } + + + /** + * + * Simple method to safely get configuration key values. + * + * @access public + * + * @param string $key The configuration key. + * + * @param mixed $default If the key does not exist, return this value + * instead. + * + * @return mixed The configuration key value (if it exists) or the + * default value (if not). + * + */ + + function getConf($key, $default = null) + { + if (isset($this->conf[$key])) { + return $this->conf[$key]; + } else { + return $default; + } + } + + + /** + * + * Extract 'attribute="value"' portions of wiki markup. + * + * This kind of markup is typically used only in macros, but is useful + * anywhere. + * + * The syntax is pretty strict; there can be no spaces between the + * option name, the equals, and the first double-quote; the value + * must be surrounded by double-quotes. You can escape characters in + * the value with a backslash, and the backslash will be stripped for + * you. + * + * @access public + * + * @param string $text The "attributes" portion of markup. + * + * @return array An associative array of key-value pairs where the + * key is the option name and the value is the option value. + * + */ + + function getAttrs($text) + { + // find the =" sections; + $tmp = explode('="', trim($text)); + + // basic setup + $k = count($tmp) - 1; + $attrs = array(); + $key = null; + + // loop through the sections + foreach ($tmp as $i => $val) { + + // first element is always the first key + if ($i == 0) { + $key = trim($val); + continue; + } + + // find the last double-quote in the value. + // the part to the left is the value for the last key, + // the part to the right is the next key name + $pos = strrpos($val, '"'); + $attrs[$key] = stripslashes(substr($val, 0, $pos)); + $key = trim(substr($val, $pos+1)); + + } + + return $attrs; + + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Creole/Address.php b/includes/pear/Text/Wiki/Parse/Creole/Address.php new file mode 100644 index 0000000..5e1eca1 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Address.php @@ -0,0 +1,67 @@ +<?php + +/** + * + * Parses for signatures. + * This class implements a Text_Wiki rule to find sections of the source + * text that are signatures. A signature is any line starting with exactly + * two - signs. + * + * @category Text + * + * @package Text_Wiki + * + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Address.php 222265 2006-10-23 13:11:27Z mic $ + * + */ + +class Text_Wiki_Parse_Address extends Text_Wiki_Parse { + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = '/^--([^-].*)$/m'; + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'start' => The starting point of the signature. + * + * 'end' => The ending point of the signature. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return "\n" . $start . trim($matches[1]) . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Blockquote.php b/includes/pear/Text/Wiki/Parse/Creole/Blockquote.php new file mode 100644 index 0000000..2ab6c14 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Blockquote.php @@ -0,0 +1,176 @@ +<?php + +/** + * + * Parse for block-quoted text. + * + * Find source text marked as a blockquote, identified by any number of + * greater-than signs '>' at the start of the line, followed by an + * optional space, and then the quote text; each '>' indicates an + * additional level of quoting. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Blockquote.php 230214 2007-02-19 08:57:14Z mic $ + * + */ + +class Text_Wiki_Parse_Blockquote extends Text_Wiki_Parse { + + + /** + * + * Regex for parsing the source text. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n(([>:]).*\n)(?!([>:]))/Us'; + + + /** + * + * Generates a replacement for the matched text. + * + * Token options are: + * + * 'type' => + * 'start' : the start of a blockquote + * 'end' : the end of a blockquote + * + * 'level' => the indent level (0 for the first level, 1 for the + * second, etc) + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A series of text and delimited tokens marking the different + * list text and list elements. + * + */ + + function process(&$matches) + { + // the replacement text we will return to parse() + $return = ''; + + // the list of post-processing matches + $list = array(); + + // $matches[1] is the text matched as a list set by parse(); + // create an array called $list that contains a new set of + // matches for the various list-item elements. + preg_match_all( + '=^([>:]+)(.*?\n)=ms', + $matches[1], + $list, + PREG_SET_ORDER + ); + + // a stack of starts and ends; we keep this so that we know what + // indent level we're at. + $stack = array(); + + // loop through each list-item element. + foreach ($list as $key => $val) { + + // $val[0] is the full matched list-item line + // $val[1] is the number of initial '>' chars (indent level) + // $val[2] is the quote text + + // we number levels starting at 1, not zero + $level = strlen($val[1]); + + // get the text of the line + $text = trim($val[2]); + + // add a level to the list? + while ($level > count($stack)) { + + $css = ($val[1][count($stack)] == ':') ? 'remark' : ''; + + // the current indent level is greater than the number + // of stack elements, so we must be starting a new + // level. push the new level onto the stack with a + // dummy value (boolean true)... + array_push($stack, true); + + $return .= "\n\n"; + + // ...and add a start token to the return. + $return .= $this->wiki->addToken( + $this->rule, + array( + 'type' => 'start', + 'level' => $level - 1, + 'css' => $css + ) + ); + + $return .= "\n\n"; + } + + // remove a level? + while (count($stack) > $level) { + + // as long as the stack count is greater than the + // current indent level, we need to end list types. + // continue adding end-list tokens until the stack count + // and the indent level are the same. + array_pop($stack); + + $return .= "\n\n"; + + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'end', + 'level' => count($stack) + ) + ); + + $return .= "\n\n"; + } + + // add the line text. + $return .= $text . "\n"; + } + + // the last line may have been indented. go through the stack + // and create end-tokens until the stack is empty. + $return .= "\n\n"; + + while (count($stack) > 0) { + array_pop($stack); + + $return .= "\n\n"; + + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'end', + 'level' => count($stack) + ) + ); + + $return .= "\n\n"; + } + + // we're done! send back the replacement text. + return "\n\n$return\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Box.php b/includes/pear/Text/Wiki/Parse/Creole/Box.php new file mode 100644 index 0000000..4660a3d --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Box.php @@ -0,0 +1,81 @@ +<?php + +/** +* +* Parses for bold text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Justin Patrin <papercrane@reversefold.com> +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Box.php 240481 2007-07-30 19:11:32Z mic $ +* +*/ + +/** +* +* Parses for bold text. +* +* This class implements a Text_Wiki_Rule to find source text marked for +* strong emphasis (bold) as defined by text surrounded by three +* single-quotes. On parsing, the text itself is left in place, but the +* starting and ending instances of three single-quotes are replaced with +* tokens. +* +* @category Text +* +* @package Text_Wiki +* +* @author Justin Patrin <papercrane@reversefold.com> +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Box extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n\[\d+\].*/s'; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken($this->rule, array('type' => 'start', 'css' => 'footnotes')); + $end = $this->wiki->addToken($this->rule, array('type' => 'end')); + return $start . $matches[0] . "\n" . $end . "\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Break.php b/includes/pear/Text/Wiki/Parse/Creole/Break.php new file mode 100644 index 0000000..47d12d9 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Break.php @@ -0,0 +1,73 @@ +<?php + +/** +* +* Parses for explicit line breaks. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Break.php 230561 2007-02-23 14:19:19Z mic $ +* +*/ + +/** +* +* Parses for explicit line breaks. +* +* This class implements a Text_Wiki_Parse to mark forced line breaks in the +* source text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Break extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + //var $regex = "/[ \n]*([\\\][\\\]|\%\%\%)[ \n]*/"; + var $regex = "/ *([\\\][\\\]|\%\%\%)\n?/"; + + + /** + * + * Generates a replacement token for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A delimited token to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + return $this->wiki->addToken($this->rule); + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Center.php b/includes/pear/Text/Wiki/Parse/Creole/Center.php new file mode 100644 index 0000000..3753aca --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Center.php @@ -0,0 +1,78 @@ +<?php + +/** + * + * Parses for centered text. + * + * This class implements a Text_Wiki_Parse to find source text marked to + * be a center element, as defined by text on a line by itself prefixed + * with an exclamation mark (!). + * The centered text itself is left in the source, but is prefixed and + * suffixed with delimited tokens marking its start and end. + * + * @category Text + * + * @package Text_Wiki + * + * @author Tomaiuolo Michele <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Center.php 240476 2007-07-30 14:22:34Z mic $ + * + */ + +class Text_Wiki_Parse_Center extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/^! *(.*?)$/m'; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * centered text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the centered text. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'start' + ) + ); + + $end = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'end' + ) + ); + + return $start . trim($matches[1]) . $end . "\n\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Creole/Delimiter.php b/includes/pear/Text/Wiki/Parse/Creole/Delimiter.php new file mode 100644 index 0000000..c53b78a --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Delimiter.php @@ -0,0 +1,68 @@ +<?php + +/** + * + * Parses for Text_Wiki delimiter characters already in the source text. + * + * This class implements a Text_Wiki_Parse to find instances of the delimiter + * character already embedded in the source text; it extracts them and replaces + * them with a delimited token, then renders them as the delimiter itself + * when the target format is XHTML. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * + * @license LGPL + * + * @version $Id: Delimiter.php 222265 2006-10-23 13:11:27Z mic $ + * + */ + +class Text_Wiki_Parse_Delimiter extends Text_Wiki_Parse { + + /** + * + * Constructor. Overrides the Text_Wiki_Parse constructor so that we + * can set the $regex property dynamically (we need to include the + * Text_Wiki $delim character. + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + * @param string $name The token name to use for this rule. + * + */ + + function Text_Wiki_Parse_Delimiter(&$obj) + { + parent::Text_Wiki_Parse($obj); + $this->regex = '/' . $this->wiki->delim . '/'; + } + + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'text' => The full matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + return $this->wiki->addToken( + $this->rule, + array('text' => $this->wiki->delim) + ); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Emphasis.php b/includes/pear/Text/Wiki/Parse/Creole/Emphasis.php new file mode 100644 index 0000000..979f89e --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Emphasis.php @@ -0,0 +1,83 @@ +<?php + +/** + * + * Parses for italic text. + * + * This class implements a Text_Wiki_Parse to find source text marked for + * emphasis (italics) as defined by text surrounded by two slashes. + * On parsing, the text itself is left in place, but the starting and ending + * instances of two single-quotes are replaced with tokens. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Emphasis.php 242125 2007-09-03 21:16:10Z mic $ + * + */ + +class Text_Wiki_Parse_Emphasis extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\/\/(.+?)\/\//"; + //var $regex = "/(?:\/\/(.+?)\/\/|(?:(?<=[\W_\xFF])\/(?![ \/]))(.+?)(?:(?<![ \/])\/(?=[\W_\xFF])))/"; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the text to be + * emphasized. + * + */ + + function process(&$matches) + { + $text = $matches[1]; + //$text = $matches[1]/* ? $matches[1] : $matches[2]*/; + + if (! $this->wiki->checkInnerTags($text)) { + return $matches[0]; + } + + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end') + ); + + return $start . $text . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Footnote.php b/includes/pear/Text/Wiki/Parse/Creole/Footnote.php new file mode 100644 index 0000000..00e4178 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Footnote.php @@ -0,0 +1,83 @@ +<?php + +/** + * + * Parses for bold text. + * + * This class implements a Text_Wiki_Rule to find source text marked for + * strong emphasis (bold) as defined by text surrounded by two + * stars. On parsing, the text itself is left in place, but the + * starting and ending instances of two stars are replaced with + * tokens. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Footnote.php 236407 2007-05-26 17:47:24Z mic $ + * + */ + +class Text_Wiki_Parse_Footnote extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/(\n)*\[([0-9]+)\]/"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $id = $matches[2]; + + if ($matches[1] == "\n") { + $matches[1] = "\n\n"; + $name = "fn$id"; + $href = "#ref$id"; + } + else { + $name = "ref$id"; + $href = "#fn$id"; + } + + $token = $this->wiki->addToken( + 'Url', + array('text' => "[$id]", 'href' => $href, 'name' => $name, 'type' => 'inline') + ); + + return $matches[1] . $token; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Heading.php b/includes/pear/Text/Wiki/Parse/Creole/Heading.php new file mode 100644 index 0000000..eb213c9 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Heading.php @@ -0,0 +1,97 @@ +<?php + +/** + * + * Parses for heading text. + * + * This class implements a Text_Wiki_Parse to find source text marked to + * be a heading element, as defined by text on a line by itself prefixed + * with a number of equasl signs (=), determining the heading level. + * Equal signs at the end of the line are silently removed. + * The heading text itself is left in the source, but is prefixed and + * suffixed with delimited tokens marking the start and end of the heading. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Tomaiuolo Michele <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Heading.php 228641 2007-02-01 09:57:36Z mic $ + * + */ + +class Text_Wiki_Parse_Heading extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/^(={1,6}) *(.*?) *=*$/m'; + + var $conf = array( + 'id_prefix' => 'toc' + ); + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * heading text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the heading text. + * + */ + + function process(&$matches) + { + // keep a running count for header IDs. we use this later + // when constructing TOC entries, etc. + static $id; + if (! isset($id)) { + $id = 0; + } + + $prefix = htmlspecialchars($this->getConf('id_prefix')); + + $start = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'start', + 'level' => strlen($matches[1]), + 'text' => trim($matches[2]), + 'id' => $prefix . $id ++ + ) + ); + + $end = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'end', + 'level' => strlen($matches[1]) + ) + ); + + return $start . trim($matches[2]) . $end . "\n\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Creole/Horiz.php b/includes/pear/Text/Wiki/Parse/Creole/Horiz.php new file mode 100644 index 0000000..60c6160 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Horiz.php @@ -0,0 +1,58 @@ +<?php + +/** + * + * Parses for horizontal ruling lines. + * + * This class implements a Text_Wiki_Parse to find source text marked to + * be a horizontal rule, as defined by four dashed on their own line. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * + * @license LGPL + * + * @version $Id: Horiz.php 228654 2007-02-01 11:35:53Z mic $ + * + */ + +class Text_Wiki_Parse_Horiz extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/^([-]{4,})$/m'; + + + /** + * + * Generates a replacement token for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token marking the horizontal rule. + * + */ + + function process(&$matches) + { + return "\n" . $this->wiki->addToken($this->rule) . "\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Image.php b/includes/pear/Text/Wiki/Parse/Creole/Image.php new file mode 100644 index 0000000..09ddb8b --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Image.php @@ -0,0 +1,69 @@ +<?php + +/** + * + * Parse for images in the source text. + * + * @category Text + * + * @package Text_Wiki + * + * @author Tomaiuolo Michele <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Image.php 243106 2007-09-28 22:02:50Z mic $ + * + */ + + +class Text_Wiki_Parse_Image extends Text_Wiki_Parse { + + /** + * + * Constructor. Overrides the Text_Wiki_Parse constructor so that we + * can set the $regex property dynamically (we need to include the + * Text_Wiki $delim character). + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + * @param string $name The token name to use for this rule. + * + */ + + function Text_Wiki_Parse_Image(&$obj) + { + parent::Text_Wiki_Parse($obj); + $this->regex = '/{{([^' . $this->wiki->delim . ']*)(\|([^' . $this->wiki->delim . ']*))?}}/U'; + } + + + /** + * + * Generates a replacement token for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token marking the horizontal rule. + * + */ + + function process(&$matches) + { + $src = trim($matches[1]); + $src = ltrim($src, '/'); + $alt = isset($matches[3]) ? trim($matches[3]) : $src; + + return $this->wiki->addToken( + $this->rule, + array( + 'src' => $src, + 'attr' => array('alt' => $alt, 'title' => $alt) + ) + ); + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/List.php b/includes/pear/Text/Wiki/Parse/Creole/List.php new file mode 100644 index 0000000..36ab590 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/List.php @@ -0,0 +1,244 @@ +<?php + +/** + * + * Parses for bulleted and numbered lists. + * + * This class implements a Text_Wiki_Parse to find source text marked as + * a bulleted or numbered list. In short, if a line starts with '*' then + * it is a bullet list item; if a line starts with '#' then it is a + * number list item. Multiple * or # indicate an indented sub-list. + * The list items must be on sequential lines, and are ended by blank lines. + * Using a non-* non-# character at the beginning of a line ends the list. + * Note that single newline characters may be eaten beforehand by other rules. + * + * @category Text + * + * @package Text_Wiki + * + * @author Justin Patrin <papercrane@reversefold.com> + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: List.php 240550 2007-08-01 07:57:34Z mic $ + * + */ + +class Text_Wiki_Parse_List extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n((\*[^\#\-\*]|\-[^\-\d\*\#]|\#[^\#\-\*]).*?)\n(?![\*\-#])/s'; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => + * 'bullet_start' : the start of a bullet list + * 'bullet_end' : the end of a bullet list + * 'number_start' : the start of a number list + * 'number_end' : the end of a number list + * 'item_start' : the start of item text (bullet or number) + * 'item_end' : the end of item text (bullet or number) + * 'unknown' : unknown type of list or item + * + * 'level' => the indent level (0 for the first level, 1 for the + * second, etc) + * + * 'count' => the list item number at this level. not needed for + * xhtml, but very useful for PDF and RTF. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A series of text and delimited tokens marking the different + * list text and list elements. + * + */ + + function process(&$matches) + { + // the replacement text we will return + $return = ''; + + // the list of post-processing matches + $list = array(); + + // a stack of list-start and list-end types; we keep this + // so that we know what kind of list we're working with + // (bullet or number) and what indent level we're at. + $stack = array(); + + // the item count is the number of list items for any + // given list-type on the stack + $itemcount = array(); + + // have we processed the very first list item? + $pastFirst = false; + + // populate $list with this set of matches. $matches[1] is the + // text matched as a list set by parse(). + preg_match_all( + '/^((\*|\-|#)+) *(.*?)$/ms', + $matches[1], + $list, + PREG_SET_ORDER + ); + + if (count($list) === 1 && $matches[0][0] === '*' && $matches[0][1] !== ' ' && strpos($matches[0], '*', 1)) { + return $matches[0]; + } + + // loop through each list-item element. + foreach ($list as $key => $val) { + // $val[0] is the full matched list-item line + // $val[1] is the level (number) + // $val[2] is the type (* or #) + // $val[3] is the list item text + + // how many levels are we indented? (1 means the "root" + // list level, no indenting.) + $stars = $val[1]; + $level = strlen($stars); + $last = $stars[strlen($stars) - 1]; + + // get the list item type + if ($last == '*' || $last == '-') { + $type = 'bullet'; + } elseif ($last == '#') { + $type = 'number'; + } else { + $type = 'unknown'; + } + + // get the text of the list item + $text = $val[3]; + + // remove a level from the list? + while (count($stack) > $level || (count($stack) == $level && $type != $stack[$level - 1])) { + + // so we don't keep counting the stack, we set up a temp + // var for the count. -1 becuase we're going to pop the + // stack in the next command. $tmp will then equal the + // current level of indent. + $tmp = count($stack) - 1; + + // as long as the stack count is greater than the + // current indent level, we need to end list types. + // continue adding end-list tokens until the stack count + // and the indent level are the same. + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => array_pop($stack) . '_list_end', + 'level' => $tmp + ) + ); + + // reset to the current (previous) list type so that + // the new list item matches the proper list type. + if ($tmp) { + $oldtype = $stack[$tmp - 1]; + } + + // reset the item count for the popped indent level + unset($itemcount[$tmp + 1]); + } + + // add a level to the list? + if ($level > count($stack)) { + + // the current indent level is greater than the + // number of stack elements, so we must be starting + // a new list. push the new list type onto the + // stack... + array_push($stack, $type); + + // ...and add a list-start token to the return. + $return .= $this->wiki->addToken( + $this->rule, + array( + 'type' => $type . '_list_start', + 'level' => $level - 1 + ) + ); + } + + // add to the item count for this list (taking into account + // which level we are at). + if (! isset($itemcount[$level])) { + // first count + $itemcount[$level] = 0; + } else { + // increment count + $itemcount[$level]++; + } + + // is this the very first item in the list? + if (! $pastFirst) { + $first = true; + $pastFirst = true; + } else { + $first = false; + } + + // create a list-item starting token. + $start = $this->wiki->addToken( + $this->rule, + array( + 'type' => $type . '_item_start', + 'level' => $level, + 'count' => $itemcount[$level], + 'first' => $first + ) + ); + + // create a list-item ending token. + $end = $this->wiki->addToken( + $this->rule, + array( + 'type' => $type . '_item_end', + 'level' => $level, + 'count' => $itemcount[$level] + ) + ); + + // add the starting token, list-item text, and ending token + // to the return. + $return .= "\n" . $start . $text . $end; + } + + // the last list-item may have been indented. go through the + // list-type stack and create end-list tokens until the stack + // is empty. + while (count($stack) > 0) { + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => array_pop($stack) . '_list_end', + 'level' => count($stack) + ) + ); + } + + // we're done! send back the replacement text. + return "\n\n" . $return . "\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Newline.php b/includes/pear/Text/Wiki/Parse/Creole/Newline.php new file mode 100644 index 0000000..92554ed --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Newline.php @@ -0,0 +1,60 @@ +<?php + +/** + * + * Parses for implied line breaks indicated by newlines. + * Newlines are not considered if followed by another newline + * or by one of these chars: * | - # = { + * + * @category Text + * + * @package Text_Wiki + * + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Newline.php 240560 2007-08-01 11:00:11Z mic $ + * + */ + +class Text_Wiki_Parse_Newline extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + //var $regex = '/(?<!\n)\n(?![\n\#\=\|\-\>\:]|\*[^\*\#]|\*+ )/m'; + var $regex = '/(?<!\n)\n(?!\n|\#|\*|\=|\||\>|\:|\;|\!|\-\D)/m'; + + + /** + * + * Generates a replacement token for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A delimited token to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + return ' '; // $this->wiki->addToken($this->rule); + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Paragraph.php b/includes/pear/Text/Wiki/Parse/Creole/Paragraph.php new file mode 100644 index 0000000..d23ed33 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Paragraph.php @@ -0,0 +1,139 @@ +<?php + +/** + * + * Parses for paragraph blocks. + * This class implements a Text_Wiki rule to find sections of the source + * text that are paragraphs. A paragraph is any line not starting with a + * token delimiter, followed by two newlines. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Paragraph.php 230214 2007-02-19 08:57:14Z mic $ + * + */ + +class Text_Wiki_Parse_Paragraph extends Text_Wiki_Parse { + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = "/^.+?\n/m"; // (?=[\n\-\|#{=]) + + var $conf = array( + 'skip' => array( + 'address', + 'box', + 'blockquote', + 'code', + 'heading', + 'center', + 'horiz', + 'deflist', + 'table', + 'list', + 'paragraph', + 'preformatted', + 'toc' + ) + ); + + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'start' => The starting point of the paragraph. + * + * 'end' => The ending point of the paragraph. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + $delim = $this->wiki->delim; + + // was anything there? + if (trim($matches[0]) == '') { + return ''; + } + + // does the match start with a delimiter? + if (substr($matches[0], 0, 1) != $delim) { + // no. + + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . trim($matches[0]) . $end; + } + + // the line starts with a delimiter. read in the delimited + // token number, check the token, and see if we should + // skip it. + + // loop starting at the second character (we already know + // the first is a delimiter) until we find another + // delimiter; the text between them is a token key number. + $key = ''; + $len = strlen($matches[0]); + for ($i = 1; $i < $len; $i++) { + $char = $matches[0]{$i}; + if ($char == $delim) { + break; + } else { + $key .= $char; + } + } + + // look at the token and see if it's skippable (if we skip, + // it will not be marked as a paragraph) + $token_type = strtolower($this->wiki->tokens[$key][0]); + $skip = $this->getConf('skip', array()); + + if (in_array($token_type, $skip)) { + // this type of token should not have paragraphs applied to it. + // return the entire matched text. + return $matches[0]; + } else { + + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . trim($matches[0]) . $end; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Prefilter.php b/includes/pear/Text/Wiki/Parse/Creole/Prefilter.php new file mode 100644 index 0000000..ced69b3 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Prefilter.php @@ -0,0 +1,54 @@ +<?php + +/** + * + * "Pre-filter" the source text. + * + * Convert DOS and Mac line endings to Unix, convert tabs to 4-spaces, + * add newlines to the top and end of the source text. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Prefilter.php 222265 2006-10-23 13:11:27Z mic $ + * + */ + +class Text_Wiki_Parse_Prefilter extends Text_Wiki_Parse { + + + /** + * + * Simple parsing method. + * + * @access public + * + */ + + function parse() + { + // convert DOS line endings + $this->wiki->source = str_replace("\r\n", "\n", + $this->wiki->source); + + // convert Macintosh line endings + $this->wiki->source = str_replace("\r", "\n", + $this->wiki->source); + + // convert tabs to four-spaces + $this->wiki->source = str_replace("\t", " ", + $this->wiki->source); + + // add extra newlines at the top and end; this + // seems to help many rules. + $this->wiki->source = "\n\n" . $this->wiki->source . "\n\n"; + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Preformatted.php b/includes/pear/Text/Wiki/Parse/Creole/Preformatted.php new file mode 100644 index 0000000..ac164fb --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Preformatted.php @@ -0,0 +1,68 @@ +<?php + +/** + * + * Parses for preformatted text. + * + * @category Text + * + * @package Text_Wiki + * + * @author Tomaiuolo Michele <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Preformatted.php 240474 2007-07-30 13:14:41Z mic $ + * + */ + +class Text_Wiki_Parse_Preformatted extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n{{{\n(.*)\n}}}\n/Us'; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'text' => The preformatted text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token to be used as a placeholder + * in the source text for the preformatted text. + * + */ + + function process(&$matches) + { + // > any line consisting of only indented three closing curly braces + // > will have one space removed from the indentation + // > -- http://www.wikicreole.org/wiki/AddNoWikiEscapeProposal + $find = "/\n( *) }}}/"; + $replace = "\n$1}}}"; + $matches[1] = preg_replace($find, $replace, $matches[1]); + + $token = $this->wiki->addToken( + $this->rule, + array('text' => $matches[1]) + ); + return "\n\n" . $token . "\n\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Creole/Raw.php b/includes/pear/Text/Wiki/Parse/Creole/Raw.php new file mode 100644 index 0000000..6679267 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Raw.php @@ -0,0 +1,61 @@ +<?php + +/** + * + * Parses for monospaced inline text. + * + * @category Text + * + * @package Text_Wiki + * + * @author Tomaiuolo Michele <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Raw.php 242165 2007-09-04 19:43:07Z mic $ + * + */ + +class Text_Wiki_Parse_Raw extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/~(_|[^ \w\n])/'; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * monospaced text. The text itself is encapsulated into a Raw token. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token to be used as a placeholder + * in the source text for the preformatted text. + * + */ + + function process(&$matches) + { + return $this->wiki->addToken( + $this->rule, + array('text' => $matches[1], 'type' => 'escape') + ); + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Creole/Strong.php b/includes/pear/Text/Wiki/Parse/Creole/Strong.php new file mode 100644 index 0000000..1d07d15 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Strong.php @@ -0,0 +1,84 @@ +<?php + +/** + * + * Parses for bold text. + * + * This class implements a Text_Wiki_Rule to find source text marked for + * strong emphasis (bold) as defined by text surrounded by two + * stars. On parsing, the text itself is left in place, but the + * starting and ending instances of two stars are replaced with + * tokens. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Strong.php 242126 2007-09-03 21:17:00Z mic $ + * + */ + +class Text_Wiki_Parse_Strong extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\*\*(.+?)\*\*/"; + //var $regex = "/(?:\*\*(.+?)\*\*|(?:(?<=[\W_\xFF])\*(?![ \*]))(.+?)(?:(?<![ \*])\*(?=[\W_\xFF])))/"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $text = $matches[1]; + //$text = $matches[1] ? $matches[1] : $matches[2]; + + if (! $this->wiki->checkInnerTags($text)) { + return $matches[0]; + } + + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end') + ); + + return $start . $text . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Subscript.php b/includes/pear/Text/Wiki/Parse/Creole/Subscript.php new file mode 100644 index 0000000..596f652 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Subscript.php @@ -0,0 +1,75 @@ +<?php + +/** + * + * Parses for italic text. + * + * This class implements a Text_Wiki_Parse to find source text marked for + * superscript as defined by text surrounded by two '^'. + * On parsing, the text itself is left in place, but the starting and ending + * instances of two '^' are replaced with tokens. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + */ + +class Text_Wiki_Parse_Subscript extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\,\,(.*?)\,\,/"; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * superscript text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the text to be + * superscripted. + * + */ + + function process(&$matches) + { + if (! $this->wiki->checkInnerTags($matches[1])) { + return $matches[0]; + } + + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end') + ); + + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Superscript.php b/includes/pear/Text/Wiki/Parse/Creole/Superscript.php new file mode 100644 index 0000000..0f5e38c --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Superscript.php @@ -0,0 +1,75 @@ +<?php + +/** + * + * Parses for italic text. + * + * This class implements a Text_Wiki_Parse to find source text marked for + * superscript as defined by text surrounded by two '^'. + * On parsing, the text itself is left in place, but the starting and ending + * instances of two '^' are replaced with tokens. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + */ + +class Text_Wiki_Parse_Superscript extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/(\^\^(.*?)\^\^|(?<=\d)(st|nd|rd|th|er|e|re|ers|res|nds|de|des|ère|ème|ères|èmes|o|a)(?!\w))/"; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * superscript text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the text to be + * superscripted. + * + */ + + function process(&$matches) + { + if (! $this->wiki->checkInnerTags($matches[0])) { + return $matches[0]; + } + + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end') + ); + + return $start . trim($matches[0], '^') . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Table.php b/includes/pear/Text/Wiki/Parse/Creole/Table.php new file mode 100644 index 0000000..2ab3284 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Table.php @@ -0,0 +1,207 @@ +<?php + +/** + * + * Parses for table markup. + * + * This class implements a Text_Wiki_Parse to find source text marked as + * a set of table rows, where a line start (and optionally ends) with a + * single-pipe (|) and uses single-pipes to separate table cells. + * The rows must be on sequential lines (no blank lines between them). + * A blank line indicates the beginning of other text or another table. + * + * @category Text + * + * @package Text_Wiki + * + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * @author Paul M. Jones <pmjones@php.net> + * + * @license LGPL + * + * @version $Id: Table.php 243077 2007-09-28 17:14:58Z mic $ + * + */ + + +class Text_Wiki_Parse_Table extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n((\|).*)(\n)(?!(\|))/Us'; + + + /** + * + * Generates a replacement for the matched text. + * + * Token options are: + * + * 'type' => + * 'table_start' : the start of a bullet list + * 'table_end' : the end of a bullet list + * 'row_start' : the start of a number list + * 'row_end' : the end of a number list + * 'cell_start' : the start of item text (bullet or number) + * 'cell_end' : the end of item text (bullet or number) + * + * 'cols' => the number of columns in the table (for 'table_start') + * + * 'rows' => the number of rows in the table (for 'table_start') + * + * 'span' => column span (for 'cell_start') + * + * 'attr' => column attribute flag (for 'cell_start') + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A series of text and delimited tokens marking the different + * table elements and cell text. + * + */ + + function process(&$matches) + { + // our eventual return value + $return = ''; + + // the number of columns in the table + $num_cols = 0; + + // the number of rows in the table + $num_rows = 0; + + // rows are separated by newlines in the matched text + $rows = explode("\n", $matches[1]); + + // loop through each row + foreach ($rows as $row) { + + // increase the row count + $num_rows ++; + + // remove first and last (optional) pipe + $row = substr($row, 1); + if ($row[strlen($row) - 1] == '|') { + $row = substr($row, 0, -1); + } + + // cells are separated by pipes + $cells = explode("|", $row); + + if (count($cells) == 1 && $cells[0][0] == '=' && ($num_rows == 1 || $num_rows == count($rows)) && ! isset($caption)) { + $caption = trim(trim($cells[0], '=')); + + // start the caption... + $return .= $this->wiki->addToken( + $this->rule, + array ('type' => 'caption_start') + ); + + // ...add the content... + $return .= $caption; + + // ...and end the caption. + $return .= $this->wiki->addToken( + $this->rule, + array ('type' => 'caption_end') + ); + } + else { + + // update the column count + if (count($cells) > $num_cols) { + $num_cols = count($cells); + } + + // start a new row + $return .= $this->wiki->addToken( + $this->rule, + array('type' => 'row_start') + ); + + for ($i = 0; $i < count($cells); $i++) { + $cell = $cells[$i]; + + // by default, cells span only one column (their own) + $span = 1; + $attr = ''; + + while ($i + 1 < count($cells) && ! strlen($cells[$i + 1])) { + $i++; + $span++; + } + + if (strlen($cell) > 0 && $cell[0] == '=') { + $attr = 'header'; + $cell = trim($cell, '='); + } + + // start a new cell... + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'cell_start', + 'attr' => $attr, + 'span' => $span + ) + ); + + // ...add the content... + $return .= trim($cell); + + // ...and end the cell. + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'cell_end', + 'attr' => $attr, + 'span' => $span + ) + ); + } + + // end the row + $return .= $this->wiki->addToken( + $this->rule, + array('type' => 'row_end') + ); + } + } + + // we're done! + return + "\n\n". + $this->wiki->addToken( + $this->rule, + array( + 'type' => 'table_start', + 'rows' => $num_rows, + 'cols' => $num_cols + ) + ). + $return. + $this->wiki->addToken( + $this->rule, + array( + 'type' => 'table_end' + ) + ). + "\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Tighten.php b/includes/pear/Text/Wiki/Parse/Creole/Tighten.php new file mode 100644 index 0000000..8ef1b45 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Tighten.php @@ -0,0 +1,37 @@ +<?php + +/** + * + * The rule removes all remaining newlines. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * + * @license LGPL + * + * @version $Id: Tighten.php 222265 2006-10-23 13:11:27Z mic $ + * + */ + + +class Text_Wiki_Parse_Tighten extends Text_Wiki_Parse { + + + /** + * + * Apply tightening directly to the source text. + * + * @access public + * + */ + + function parse() + { + $this->wiki->source = str_replace("\n", '', + $this->wiki->source); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Trim.php b/includes/pear/Text/Wiki/Parse/Creole/Trim.php new file mode 100644 index 0000000..f10c9a2 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Trim.php @@ -0,0 +1,75 @@ +<?php + +/** + * + * Trim lines in the source text and compress 3 or more newlines to + * 2 newlines. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + */ + +class Text_Wiki_Parse_Trim extends Text_Wiki_Parse { + + + /** + * + * Simple parsing method. + * + * @access public + * + */ + + function parse() + { + // trim lines + $find = "/ *\n */"; + $replace = "\n"; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + + // trim lines with only one dash or star + $find = "/\n[\-\*]\n/"; + $replace = "\n\n"; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + + // finally, compress all instances of 3 or more newlines + // down to two newlines. + $find = "/\n{3,}/m"; + $replace = "\n\n"; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + + // numbered lists + $find = "/(\n[\*\#]*)([\d]+[\.\)]|[\w]\)) /s"; + $replace = "$1# "; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + + // numbers in parentesis are footnotes and references + $find = "/\(([\d][\d]?)\)/"; + $replace = "[$1]"; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + + // add hr before footnotes + $find = "/(\n+\-\-\-\-+\n*)?(\n\[[\d]+\].*)/s"; + $replace = "\n\n----\n\n$2"; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + + /* + // wrap images in tables + $find = "/(?<=\n\n){{([^\|}]*)\|([^}]*)}}(?=\n\n)/"; + $replace = "| {{ $1 | $2 }}\n|= $2"; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + + // wrap images in tables + $find = "/(?<=\n\n){{([^\|}]*)}}(?=\n\n)/"; + $replace = "| {{ $1 }}"; + $this->wiki->source = preg_replace($find, $replace, $this->wiki->source); + */ + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Tt.php b/includes/pear/Text/Wiki/Parse/Creole/Tt.php new file mode 100644 index 0000000..31072bc --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Tt.php @@ -0,0 +1,78 @@ +<?php + +/** + * + * Parses for monospaced inline text. + * + * @category Text + * + * @package Text_Wiki + * + * @author Tomaiuolo Michele <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Tt.php 240474 2007-07-30 13:14:41Z mic $ + * + */ + +class Text_Wiki_Parse_Tt extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/{{{(.*?)}}}(?!}|{{{)/'; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * monospaced text. The text itself is encapsulated into a Raw token. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token to be used as a placeholder + * in the source text for the preformatted text. + * + */ + + function process(&$matches) + { + // remove the sequence }}}{{{ + $find = "/}}}{{{/"; + $replace = ""; + $matches[1] = preg_replace($find, $replace, $matches[1]); + + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start') + ); + + $raw = $this->wiki->addToken( + 'Raw', + array('text' => $matches[1]) + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end') + ); + + return $start . $raw . $end; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Creole/Underline.php b/includes/pear/Text/Wiki/Parse/Creole/Underline.php new file mode 100644 index 0000000..c3d5b14 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Underline.php @@ -0,0 +1,83 @@ +<?php + +/** + * + * Parses for italic text. + * + * This class implements a Text_Wiki_Parse to find source text marked for + * underlined as defined by text surrounded by two '_'. + * On parsing, the text itself is left in place, but the starting and ending + * instances of two '^' are replaced with tokens. + * + * @category Text + * + * @package Text_Wiki + * + * @author Paul M. Jones <pmjones@php.net> + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Underline.php 242127 2007-09-03 21:29:36Z mic $ + * + */ + +class Text_Wiki_Parse_Underline extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/__(.+?)__/"; + //var $regex = "/(?:\_\_(.+?)\_\_|(?:(?<=[\W_\xFF])\_(?![ \_]))(.+?)(?:(?<![ \_])\_(?=[\W_\xFF])))/"; + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * superscript text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the text to be + * superscripted. + * + */ + + function process(&$matches) + { + $text = $matches[1]; + //$text = $matches[1] ? $matches[1] : $matches[2]; + + if (! $this->wiki->checkInnerTags($text)) { + return $matches[0]; + } + + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end') + ); + + return $start . $text . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Url.php b/includes/pear/Text/Wiki/Parse/Creole/Url.php new file mode 100644 index 0000000..4422a31 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Url.php @@ -0,0 +1,109 @@ +<?php + +/** + * + * Parse for URLS in the source text. + * + * raw -- http://example.com + * no descr. -- [[http://example.com]] + * described -- [[http://example.com|Example Description]] + * + * When rendering a URL token, this will convert URLs pointing to a .gif, + * .jpg, or .png image into an inline <img /> tag (for the 'xhtml' + * format). + * + * @category Text + * + * @package Text_Wiki + * + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license LGPL + * + * @version $Id: Url.php 293784 2010-01-20 18:48:09Z justinpatrin $ + * + */ + +class Text_Wiki_Parse_Url extends Text_Wiki_Parse { + + /** + * + * Constructor. Overrides the Text_Wiki_Parse constructor so that we + * can set the $regex property dynamically (we need to include the + * Text_Wiki $delim character). + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + * @param string $name The token name to use for this rule. + * + */ + + function Text_Wiki_Parse_Url(&$obj) + { + parent::Text_Wiki_Parse($obj); + $this->regex = '/((?:\[\[ *((?:\w+:\/\/|mailto:|\/)[^\|\]\n ]*)( *\| *([^\]\n]*))? *\]\])|((?<=[^\~\w])(https?:\/\/|ftps?:\/\/|mailto:)[^\'\"\n ' . $this->wiki->delim . ']*[A-Za-z0-9\/\?\=\&\~\_#]))/'; + } + + + /** + * + * Generates a replacement for the matched text. + * + * Token options are: + * + * 'href' => the URL link href portion + * + * 'text' => the displayed text of the URL link + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token to be used as a placeholder + * in the source text for the preformatted text. + * + */ + + function process(&$matches) + { + if (isset($matches[2])) $href = trim($matches[2]); + if (isset($matches[4])) $text = trim($matches[4]); + if (isset($matches[5])) $rawurl = $matches[5]; + if (empty($href)) $href = $rawurl; + + if (empty($text)) { + $text = $href; + if (strpos($text, '/') === FALSE) { + $text = str_replace('http://', '', $text); + $text = str_replace('mailto:', '', $text); + } + return $this->wiki->addToken( + $this->rule, + array( + 'type' => 'inline', + 'href' => $href, + 'text' => $text + ) + ); + } else { + return $this->wiki->addToken( + $this->rule, + array( + 'type' => 'start', + 'href' => $href, + 'text' => $text + ) + ) . $text . + $this->wiki->addToken( + $this->rule, + array( + 'type' => 'end', + 'href' => $href, + 'text' => $text + ) + ); + } + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Creole/Wikilink.php b/includes/pear/Text/Wiki/Parse/Creole/Wikilink.php new file mode 100644 index 0000000..19cafc1 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Creole/Wikilink.php @@ -0,0 +1,322 @@ +<?php + +/** + * Mediawiki: Parses for links to (inter)wiki pages or images. + * + * Text_Wiki rule parser to find links, it groups the 3 rules: + * # Wikilink: links to internal Wiki pages + * # Interwiki: links to external Wiki pages (sister projects, interlangage) + * # Image: Images + * as defined by text surrounded by double brackets [[]] + * Translated are the link itself, the section (anchor) and alternate text + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @author Paul M. Jones <pmjones@php.net> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Wikilink.php 240474 2007-07-30 13:14:41Z mic $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Wikilink, Interwiki and Image rules parser class for Mediawiki. + * This class implements a Text_Wiki_Parse to find links marked + * in source by text surrounded by 2 opening/closing brackets as + * [[Wiki page name#Section|Alternate text]] + * On parsing, the link is replaced with a token. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki_Parse::Text_Wiki_Parse() + */ + +class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse { + + /** + * Configuration for this rule (Wikilink) + * + * @access public + * @var array + */ + + var $conf = array( + 'spaceUnderscore' => true, + 'project' => array('demo', 'd'), + 'url' => 'http://example.com/en/page=%s', + 'langage' => 'en' + ); + + /** + * Configuration for the Image rule + * + * @access public + * @var array + */ + + var $imageConf = array( + 'prefix' => array('Image', 'image') + ); + + /** + * Configuration for the Interwiki rule + * + * @access public + * @var array + */ + + var $interwikiConf = array( + 'sites' => array( + 'manual' => 'http://www.php.net/manual/en/%s', + 'pear' => 'http://pear.php.net/package/%s', + 'bugs' => 'http://pear.php.net/package/%s/bugs' + ), + 'interlangage' => array('en', 'de', 'fr') + ); + + /** + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * @var string + * @see Text_Wiki_Parse::parse() + */ + + var $regex = '/(?<!\[)\[\[(?!\[) *(:?)((?:[^:\n]+:)+)?([^:\n]+)(#(?:[^\n]*))?(?: *\| *(((?R))|[^\n]*))? *]]/msU'; + + /** + * Constructor. + * We override the constructor to get Image and Interwiki config + * + * @param object &$obj the base conversion handler + * @return The parser object + * @access public + */ + + function Text_Wiki_Parse_Wikilink(&$obj) + { + $default = $this->conf; + parent::Text_Wiki_Parse($obj); + + // override config options for image if specified + if (in_array('Image', $this->wiki->disable)) { + $this->imageConf['prefix'] = array(); + } else { + if (isset($this->wiki->parseConf['Image']) && + is_array($this->wiki->parseConf['Image'])) { + $this->imageConf = array_merge( + $this->imageConf, + $this->wiki->parseConf['Image'] + ); + } + } + + // override config options for interwiki if specified + if (in_array('Interwiki', $this->wiki->disable)) { + $this->interwikiConf['sites'] = array(); + $this->interwikiConf['interlangage'] = array(); + } else { + if (isset($this->wiki->parseConf['Interwiki']) && + is_array($this->wiki->parseConf['Interwiki'])) { + $this->interwikiConf = array_merge( + $this->interwikiConf, + $this->wiki->parseConf['Interwiki'] + ); + } + if (empty($this->conf['langage'])) { + $this->interwikiConf['interlangage'] = array(); + } + } + //$this->regex = str_replace('DELIM', $this->wiki->delim, $this->regex); + // convert the list of recognized schemes to a regex OR, +/* $schemes = $this->getConf('schemes', $default['schemes']); + $this->url = str_replace( '#delim#', $this->wiki->delim, + '#(?:' . (is_array($schemes) ? implode('|', $schemes) : $schemes) . ')://' + . $this->getConf('host_regexp', $default['host_regexp']) + . $this->getConf('path_regexp', $default['path_regexp']) .'#'); */ + } + + /** + * Generates a replacement for the matched text. Token options are: + * - 'page' => the name of the target wiki page + * -'anchor' => the optional section in it + * - 'text' => the optional alternate link text + * + * @access public + * @param array &$matches The array of matches from parse(). + * @return string token to be used as replacement + */ + + function process(&$matches) + { + $matches[3] = $this->wiki->restoreRaw($matches[3]); + + // Starting colon ? + $colon = !empty($matches[1]); + $auto = $interlang = $interwiki = $image = $site = ''; + // Prefix ? + if (!empty($matches[2])) { + $prefix = explode(':', substr($matches[2], 0, -1)); + $count = count($prefix); + $i = -1; + // Autolink + if (isset($this->conf['project']) && + in_array(trim($prefix[0]), $this->conf['project'])) { + $auto = trim($prefix[0]); + unset($prefix[0]); + $i = 0; + } + while (++$i < $count) { + $prefix[$i] = trim($prefix[$i]); + // interlangage + if (!$interlang && + in_array($prefix[$i], $this->interwikiConf['interlangage'])) { + $interlang = $prefix[$i]; + unset($prefix[$i]); + continue; + } + // image + if (!$image && in_array($prefix[$i], $this->imageConf['prefix'])) { + $image = $prefix[$i]; + unset($prefix[$i]); + break; + } + // interwiki + if (isset($this->interwikiConf['sites'][$prefix[$i]])) { + $interwiki = $this->interwikiConf['sites'][$prefix[$i]]; + $site = $prefix[$i]; + unset($prefix[$i]); + } + break; + } + if ($prefix) { + $matches[3] = implode(':', $prefix) . ':' . $matches[3]; + } + } + $text = empty($matches[5]) ? $matches[3] : $matches[5]; + $matches[3] = trim($matches[3]); + $matches[4] = empty($matches[4]) ? '' : trim($matches[4]); + if ($this->conf['spaceUnderscore']) { + $matches[3] = preg_replace('/\s+/', '_', $matches[3]); + $matches[4] = preg_replace('/\s+/', '_', $matches[4]); + } + if ($image) { + return $this->image($matches[3] . (empty($matches[4]) ? '' : '#' . $matches[4]), + $text, $interlang, $colon); + } + if (!$interwiki && $interlang && isset($this->conf['url'])) { + if ($interlang == $this->conf['langage']) { + $interlang = ''; + } else { + $interwiki = $this->conf['url']; + $site = isset($this->conf['project']) ? $this->conf['project'][0] : ''; + } + } + if ($interwiki) { + return $this->interwiki($site, $interwiki, + $matches[3] . (empty($matches[4]) ? '' : '#' . $matches[4]), + $text, $interlang, $colon); + } + if ($interlang) { + $matches[3] = $interlang . ':' . $matches[3]; + $text = (empty($matches[5]) ? $interlang . ':' : '') . $text; + } + + $start = $this->wiki->addToken($this->rule, array( + 'type' => 'start', + 'page' => $matches[3], + 'anchor' => (empty($matches[4]) ? '' : $matches[4]), + 'text' => $text + )); + + $end = $this->wiki->addToken($this->rule, array( + 'type' => 'end', + 'page' => $matches[3], + 'anchor' => (empty($matches[4]) ? '' : $matches[4]), + 'text' => $text + )); + + // create and return the replacement token + return $start . $text . $end; + } + + /** + * Generates an image token. Token options are: + * - 'src' => the name of the image file + * - 'attr' => an array of attributes for the image: + * | - 'alt' => the optional alternate image text + * | - 'align => 'left', 'center' or 'right' + * + * @access public + * @param array &$matches The array of matches from parse(). + * @return string token to be used as replacement + */ + + function image($name, $text, $interlang, $colon) + { + $attr = array('alt' => ''); + // scan text for supplementary attibutes + if (strpos($text, '|') !== false) { + $splits = explode('|', $text); + $sep = ''; + foreach ($splits as $split) { + switch (strtolower($split)) { + case 'left': case 'center': case 'right': + $attr['align'] = strtolower($split); + break; + default: + $attr['alt'] .= $sep . $split; + $sep = '|'; + } + } + } else { + $attr['alt'] = $text; + } + $options = array( + 'src' => ($interlang ? $interlang . ':' : '') . $name, + 'attr' => $attr); + + // create and return the replacement token + return $this->wiki->addToken('Image', $options); + } + + /** + * Generates an interwiki token. Token options are: + * - 'page' => the name of the target wiki page + * - 'site' => the key for external site + * - 'url' => the full target url + * - 'text' => the optional alternate link text + * + * @access public + * @param array &$matches The array of matches from parse(). + * @return string token to be used as replacement + */ + + function interwiki($site, $interwiki, $page, $text, $interlang, $colon) + { + if ($interlang) { + $interwiki = preg_replace('/\b' . $this->conf['langage'] . '\b/i', + $interlang, $interwiki); + } + // set the options + $options = array( + 'page' => $page, + 'site' => $site, + 'url' => sprintf($interwiki, $page), + 'text' => $text + ); + + // create and return the replacement token + return $this->wiki->addToken('Interwiki', $options); + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Anchor.php b/includes/pear/Text/Wiki/Parse/Default/Anchor.php new file mode 100644 index 0000000..83b57c6 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Anchor.php @@ -0,0 +1,87 @@ +<?php + +/** +* +* Parses for anchor targets. +* +* @category Text +* +* @package Text_Wiki +* +* @author Manuel Holtgrewe <purestorm at ggnore dot net> +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Anchor.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* This class implements a Text_Wiki_Parse to add an anchor target name +* in the wiki page. +* +* @author Manuel Holtgrewe <purestorm at ggnore dot net> +* +* @author Paul M. Jones <pmjones at ciaweb dot net> +* +* @category Text +* +* @package Text_Wiki +* +*/ + +class Text_Wiki_Parse_Anchor extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to find source text matching this + * rule. Looks like a macro: [[# anchor_name]] + * + * @access public + * + * @var string + * + */ + + var $regex = '/(\[\[# )([-_A-Za-z0-9.]+?)( .+)?(\]\])/i'; + + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'text' => The full matched text, not including the <code></code> tags. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) { + + $name = $matches[2]; + $text = $matches[3]; + + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start', 'name' => $name) + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end', 'name' => $name) + ); + + // done, place the script output directly in the source + return $start . trim($text) . $end; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Blockquote.php b/includes/pear/Text/Wiki/Parse/Default/Blockquote.php new file mode 100644 index 0000000..61da770 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Blockquote.php @@ -0,0 +1,180 @@ +<?php + +/** +* +* Parse for block-quoted text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Blockquote.php 222150 2006-10-21 05:56:28Z justinpatrin $ +* +*/ + +/** +* +* Parse for block-quoted text. +* +* Find source text marked as a blockquote, identified by any number of +* greater-than signs '>' at the start of the line, followed by a space, +* and then the quote text; each '>' indicates an additional level of +* quoting. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Blockquote extends Text_Wiki_Parse { + + + /** + * + * Regex for parsing the source text. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n((\>).*\n)(?!(\>))/Us'; + + + /** + * + * Generates a replacement for the matched text. + * + * Token options are: + * + * 'type' => + * 'start' : the start of a blockquote + * 'end' : the end of a blockquote + * + * 'level' => the indent level (0 for the first level, 1 for the + * second, etc) + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A series of text and delimited tokens marking the different + * list text and list elements. + * + */ + + function process(&$matches) + { + // the replacement text we will return to parse() + $return = "\n"; + + // the list of post-processing matches + $list = array(); + + // $matches[1] is the text matched as a list set by parse(); + // create an array called $list that contains a new set of + // matches for the various list-item elements. + preg_match_all( + '=^(\>+) (.*\n)=Ums', + $matches[1], + $list, + PREG_SET_ORDER + ); + + $curLevel = 0; + + // loop through each list-item element. + foreach ($list as $key => $val) { + + // $val[0] is the full matched list-item line + // $val[1] is the number of initial '>' chars (indent level) + // $val[2] is the quote text + + // we number levels starting at 1, not zero + $level = strlen($val[1]); + + // add a level to the list? + while ($level > $curLevel) { + // the current indent level is greater than the number + // of stack elements, so we must be starting a new + // level. push the new level onto the stack with a + // dummy value (boolean true)... + ++$curLevel; + + //$return .= "\n"; + + // ...and add a start token to the return. + $return .= $this->wiki->addToken( + $this->rule, + array( + 'type' => 'start', + 'level' => $curLevel + ) + ); + + //$return .= "\n\n"; + } + + // remove a level? + while ($curLevel > $level) { + + // as long as the stack count is greater than the + // current indent level, we need to end list types. + // continue adding end-list tokens until the stack count + // and the indent level are the same. + + //$return .= "\n\n"; + + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'end', + 'level' => $curLevel + ) + ); + + //$return .= "\n"; + --$curLevel; + } + + // add the line text. + $return .= $val[2]; + } + + // the last char of the matched pattern must be \n but we don't + // want this to be inside the tokens + $return = substr($return, 0, -1); + + // the last line may have been indented. go through the stack + // and create end-tokens until the stack is empty. + //$return .= "\n"; + + while ($curLevel > 0) { + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'end', + 'level' => $curLevel + ) + ); + --$curLevel; + } + + // put back the trailing \n + $return .= "\n"; + + // we're done! send back the replacement text. + return $return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Bold.php b/includes/pear/Text/Wiki/Parse/Default/Bold.php new file mode 100644 index 0000000..ba5e965 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Bold.php @@ -0,0 +1,79 @@ +<?php + +/** +* +* Parses for bold text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Bold.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for bold text. +* +* This class implements a Text_Wiki_Rule to find source text marked for +* strong emphasis (bold) as defined by text surrounded by three +* single-quotes. On parsing, the text itself is left in place, but the +* starting and ending instances of three single-quotes are replaced with +* tokens. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Bold extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/'''(()|[^'].*)'''/U"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken($this->rule, array('type' => 'start')); + $end = $this->wiki->addToken($this->rule, array('type' => 'end')); + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Break.php b/includes/pear/Text/Wiki/Parse/Default/Break.php new file mode 100644 index 0000000..1a684f1 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Break.php @@ -0,0 +1,72 @@ +<?php + +/** +* +* Parses for explicit line breaks. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Break.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for explicit line breaks. +* +* This class implements a Text_Wiki_Parse to mark forced line breaks in the +* source text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Break extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/ _\n/'; + + + /** + * + * Generates a replacement token for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A delimited token to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + return $this->wiki->addToken($this->rule); + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Center.php b/includes/pear/Text/Wiki/Parse/Default/Center.php new file mode 100644 index 0000000..6ddde51 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Center.php @@ -0,0 +1,78 @@ +<?php + +/** +* +* Parses for centered lines of text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Center.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for centered lines of text. +* +* This class implements a Text_Wiki_Parse to find lines marked for centering. +* The line must start with "= " (i.e., an equal-sign followed by a space). +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Center extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = '/\n\= (.*?)\n/'; + + /** + * + * Generates a token entry for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, + array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, + array('type' => 'end') + ); + + return "\n" . $start . $matches[1] . $end . "\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Code.php b/includes/pear/Text/Wiki/Parse/Default/Code.php new file mode 100644 index 0000000..fc2d3c1 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Code.php @@ -0,0 +1,99 @@ +<?php + +/** +* +* Parses for text marked as a code example block. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Code.php 237313 2007-06-09 23:11:25Z justinpatrin $ +* +*/ + +/** +* +* Parses for text marked as a code example block. +* +* This class implements a Text_Wiki_Parse to find sections marked as code +* examples. Blocks are marked as the string <code> on a line by itself, +* followed by the inline code example, and terminated with the string +* </code> on a line by itself. The code example is run through the +* native PHP highlight_string() function to colorize it, then surrounded +* with <pre>...</pre> tags when rendered as XHTML. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Code extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + +/* var $regex = '/^(\<code( .+)?\>)\n(.+)\n(\<\/code\>)(\s|$)/Umsi';*/ + var $regex = ';^<code(\s[^>]*)?>((?:(?R)|.*?)*)\n</code>(\s|$);msi'; + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'text' => The full matched text, not including the <code></code> tags. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + // are there additional attribute arguments? + $args = trim($matches[1]); + + if ($args == '') { + $options = array( + 'text' => $matches[2], + 'attr' => array('type' => '') + ); + } else { + // get the attributes... + $attr = $this->getAttrs($args); + + // ... and make sure we have a 'type' + if (! isset($attr['type'])) { + $attr['type'] = ''; + } + + // retain the options + $options = array( + 'text' => $matches[2], + 'attr' => $attr + ); + } + + return $this->wiki->addToken($this->rule, $options) . $matches[3]; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Colortext.php b/includes/pear/Text/Wiki/Parse/Default/Colortext.php new file mode 100644 index 0000000..5d82cd6 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Colortext.php @@ -0,0 +1,89 @@ +<?php + +/** +* +* Parses for colorized text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Colortext.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for colorized text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Colortext extends Text_Wiki_Parse { + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\#\#(.+?)\|(.+?)\#\#/"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * 'color' => the color indicator + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the text to be + * emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'start', + 'color' => $matches[1] + ) + ); + + $end = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'end', + 'color' => $matches[1] + ) + ); + + return $start . $matches[2] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Deflist.php b/includes/pear/Text/Wiki/Parse/Default/Deflist.php new file mode 100644 index 0000000..a8ee0fb --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Deflist.php @@ -0,0 +1,122 @@ +<?php + +/** +* +* Parses for definition lists. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Deflist.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for definition lists. +* +* This class implements a Text_Wiki_Parse to find source text marked as a +* definition list. In short, if a line starts with ':' then it is a +* definition list item; another ':' on the same line indicates the end +* of the definition term and the beginning of the definition narrative. +* The list items must be on sequential lines (no blank lines between +* them) -- a blank line indicates the beginning of a new list. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Deflist extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n((: ).*\n)(?!(: |\n))/Us'; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => + * 'list_start' : the start of a definition list + * 'list_end' : the end of a definition list + * 'term_start' : the start of a definition term + * 'term_end' : the end of a definition term + * 'narr_start' : the start of definition narrative + * 'narr_end' : the end of definition narrative + * 'unknown' : unknown type of definition portion + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A series of text and delimited tokens marking the different + * list text and list elements. + * + */ + + function process(&$matches) + { + // the replacement text we will return to parse() + $return = ''; + + // the list of post-processing matches + $list = array(); + + // start the deflist + $options = array('type' => 'list_start'); + $return .= $this->wiki->addToken($this->rule, $options); + + // $matches[1] is the text matched as a list set by parse(); + // create an array called $list that contains a new set of + // matches for the various definition-list elements. + preg_match_all( + '/^(: )(.*)?( : )(.*)?$/Ums', + $matches[1], + $list, + PREG_SET_ORDER + ); + + // add each term and narrative + foreach ($list as $key => $val) { + $return .= ( + $this->wiki->addToken($this->rule, array('type' => 'term_start')) . + trim($val[2]) . + $this->wiki->addToken($this->rule, array('type' => 'term_end')) . + $this->wiki->addToken($this->rule, array('type' => 'narr_start')) . + trim($val[4]) . + $this->wiki->addToken($this->rule, array('type' => 'narr_end')) + ); + } + + + // end the deflist + $options = array('type' => 'list_end'); + $return .= $this->wiki->addToken($this->rule, $options); + + // done! + return "\n" . $return . "\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Delimiter.php b/includes/pear/Text/Wiki/Parse/Default/Delimiter.php new file mode 100644 index 0000000..8aa4b53 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Delimiter.php @@ -0,0 +1,80 @@ +<?php + +/** +* +* Parses for Text_Wiki delimiter characters already in the source text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Delimiter.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for Text_Wiki delimiter characters already in the source text. +* +* This class implements a Text_Wiki_Parse to find instances of the delimiter +* character already embedded in the source text; it extracts them and replaces +* them with a delimited token, then renders them as the delimiter itself +* when the target format is XHTML. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Delimiter extends Text_Wiki_Parse { + + /** + * + * Constructor. Overrides the Text_Wiki_Parse constructor so that we + * can set the $regex property dynamically (we need to include the + * Text_Wiki $delim character. + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + * @param string $name The token name to use for this rule. + * + */ + + function Text_Wiki_Parse_delimiter(&$obj) + { + parent::Text_Wiki_Parse($obj); + $this->regex = '/' . $this->wiki->delim . '/'; + } + + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'text' => The full matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + return $this->wiki->addToken( + $this->rule, + array('text' => $this->wiki->delim) + ); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Embed.php b/includes/pear/Text/Wiki/Parse/Default/Embed.php new file mode 100644 index 0000000..2d31a71 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Embed.php @@ -0,0 +1,106 @@ +<?php + +/** +* +* Embeds the results of a PHP script at render-time. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Embed.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Embeds the results of a PHP script at render-time. +* +* This class implements a Text_Wiki_Parse to embed the contents of a URL +* inside the page at render-time. Typically used to get script output. +* This differs from the 'include' rule, which incorporates results at +* parse-time; 'embed' output does not get parsed by Text_Wiki, while +* 'include' ouput does. +* +* This rule is inherently not secure; it allows cross-site scripting to +* occur if the embedded output has <script> or other similar tags. Be +* careful. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Embed extends Text_Wiki_Parse { + + var $conf = array( + 'base' => '/path/to/scripts/' + ); + + var $file = null; + + var $output = null; + + var $vars = null; + + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = '/(\[\[embed )(.+?)( .+?)?(\]\])/i'; + + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'text' => The full matched text, not including the <code></code> tags. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + // save the file location + $this->file = $this->getConf('base', './') . $matches[2]; + + // extract attribs as variables in the local space + $this->vars = $this->getAttrs($matches[3]); + unset($this->vars['this']); + extract($this->vars); + + // run the script + ob_start(); + include($this->file); + $this->output = ob_get_contents(); + ob_end_clean(); + + // done, place the script output directly in the source + return $this->wiki->addToken( + $this->rule, + array('text' => $this->output) + ); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Emphasis.php b/includes/pear/Text/Wiki/Parse/Default/Emphasis.php new file mode 100644 index 0000000..85dd212 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Emphasis.php @@ -0,0 +1,85 @@ +<?php + +/** +* +* Parses for emphasized text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Emphasis.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for emphasized text. +* +* This class implements a Text_Wiki_Parse to find source text marked for +* emphasis (italics) as defined by text surrounded by two single-quotes. +* On parsing, the text itself is left in place, but the starting and ending +* instances of two single-quotes are replaced with tokens. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_emphasis extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\/\/(.*?)\/\//"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the text to be + * emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Freelink.php b/includes/pear/Text/Wiki/Parse/Default/Freelink.php new file mode 100644 index 0000000..6cc0bc6 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Freelink.php @@ -0,0 +1,134 @@ +<?php + +/** +* +* Parses for wiki freelink text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Freelink.php 224598 2006-12-08 08:23:51Z justinpatrin $ +* +*/ + +/** +* +* Parses for freelinked page links. +* +* This class implements a Text_Wiki_Parse to find source text marked as a +* wiki freelink, and automatically create a link to that page. +* +* A freelink is any page name not conforming to the standard +* StudlyCapsStyle for a wiki page name. For example, a page normally +* named MyHomePage can be renamed and referred to as ((My Home Page)) -- +* note the spaces in the page name. You can also make a "nice-looking" +* link without renaming the target page; e.g., ((MyHomePage|My Home +* Page)). Finally, you can use named anchors on the target page: +* ((MyHomePage|My Home Page#Section1)). +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Freelink extends Text_Wiki_Parse { + + var $conf = array ( + 'utf-8' => false + ); + + /** + * + * Constructor. We override the Text_Wiki_Parse constructor so we can + * explicitly comment each part of the $regex property. + * + * @access public + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + */ + + function Text_Wiki_Parse_Freelink(&$obj) + { + parent::Text_Wiki_Parse($obj); + if ($this->getConf('utf-8')) { + $any = '\p{L}'; + } else { + $any = ''; + } + $this->regex = + '/' . // START regex + "\\(\\(" . // double open-parens + "(" . // START freelink page patter + "[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&".$any."\xc0-\xff]+" . // 1 or more of just about any character + ")" . // END freelink page pattern + "(" . // START display-name + "\|" . // a pipe to start the display name + "[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&".$any."\xc0-\xff]+" . // 1 or more of just about any character + ")?" . // END display-name pattern 0 or 1 + "(" . // START pattern for named anchors + "\#" . // a hash mark + "[A-Za-z]" . // 1 alpha + "[-A-Za-z0-9_:.]*" . // 0 or more alpha, digit, underscore + ")?" . // END named anchors pattern 0 or 1 + "()\\)\\)" . // double close-parens + '/'.($this->getConf('utf-8') ? 'u' : ''); // END regex + } + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'page' => the wiki page name (e.g., HomePage). + * + * 'text' => alternative text to be displayed in place of the wiki + * page name. + * + * 'anchor' => a named anchor on the target wiki page + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token to be used as a placeholder in + * the source text, plus any text priot to the match. + * + */ + + function process(&$matches) + { + // use nice variable names + $page = $matches[1]; + $text = $matches[2]; + $anchor = $matches[3]; + + // is the page given a new text appearance? + if (trim($text) == '') { + // no + $text = $page; + } else { + // yes, strip the leading | character + $text = substr($text, 1); + } + + // set the options + $options = array( + 'page' => $page, + 'text' => $text, + 'anchor' => $anchor + ); + + // return a token placeholder + return $this->wiki->addToken($this->rule, $options); + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Function.php b/includes/pear/Text/Wiki/Parse/Default/Function.php new file mode 100644 index 0000000..35580bf --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Function.php @@ -0,0 +1,141 @@ +<?php + +/** +* +* Parses for an API function documentation block. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Function.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for an API function documentation block. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Function extends Text_Wiki_Parse { + + var $regex = '/^(\<function\>)\n(.+)\n(\<\/function\>)(\s|$)/Umsi'; + + function process(&$matches) + { + // default options + $opts = array( + 'name' => null, + 'access' => null, + 'return' => null, + 'params' => array(), + 'throws' => array() + ); + + // split apart the markup lines and loop through them + $lines = explode("\n", $matches[2]); + foreach ($lines as $line) { + + // skip blank lines + if (trim($line) == '') { + continue; + } + + // find the first ':' on the line; the left part is the + // type, the right part is the value. skip lines without + // a ':' on them. + $pos = strpos($line, ':'); + if ($pos === false) { + continue; + } + + // $type is the line type: name, access, return, param, throws + // 012345678901234 + // name: something + $type = trim(substr($line, 0, $pos)); + $val = trim(substr($line, $pos+1)); + + switch($type) { + + case 'a': + case 'access': + $opts['access'] = $val; + break; + + case 'n': + case 'name': + $opts['name'] = $val; + break; + + case 'p': + case 'param': + $tmp = explode(',', $val); + $k = count($tmp); + if ($k == 1) { + $opts['params'][] = array( + 'type' => $tmp[0], + 'descr' => null, + 'default' => null + ); + } elseif ($k == 2) { + $opts['params'][] = array( + 'type' => $tmp[0], + 'descr' => $tmp[1], + 'default' => null + ); + } else { + $opts['params'][] = array( + 'type' => $tmp[0], + 'descr' => $tmp[1], + 'default' => $tmp[2] + ); + } + break; + + case 'r': + case 'return': + case 'returns': + $opts['return'] = $val; + break; + + case 't': + case 'throws': + $tmp = explode(',', $val); + $k = count($tmp); + if ($k == 1) { + $opts['throws'][] = array( + 'type' => $tmp[0], + 'descr' => null + ); + } else { + $opts['throws'][] = array( + 'type' => $tmp[0], + 'descr' => $tmp[1] + ); + } + break; + + default: + $opts[$type] = $val; + break; + + } + } + + // add the token back in place + return $this->wiki->addToken($this->rule, $opts) . $matches[4]; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Heading.php b/includes/pear/Text/Wiki/Parse/Default/Heading.php new file mode 100644 index 0000000..35727e9 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Heading.php @@ -0,0 +1,107 @@ +<?php + +/** +* +* Parses for heading text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Heading.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for heading text. +* +* This class implements a Text_Wiki_Parse to find source text marked to +* be a heading element, as defined by text on a line by itself prefixed +* with a number of plus signs (+). The heading text itself is left in +* the source, but is prefixed and suffixed with delimited tokens marking +* the start and end of the heading. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Heading extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/^(\+{1,6}) (.*)/m'; + + var $conf = array( + 'id_prefix' => 'toc' + ); + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * heading text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the heading text. + * + */ + + function process(&$matches) + { + // keep a running count for header IDs. we use this later + // when constructing TOC entries, etc. + static $id; + if (! isset($id)) { + $id = 0; + } + + $prefix = htmlspecialchars($this->getConf('id_prefix')); + + $start = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'start', + 'level' => strlen($matches[1]), + 'text' => $matches[2], + 'id' => $prefix . $id ++ + ) + ); + + $end = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'end', + 'level' => strlen($matches[1]) + ) + ); + + return $start . $matches[2] . $end . "\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Horiz.php b/includes/pear/Text/Wiki/Parse/Default/Horiz.php new file mode 100644 index 0000000..a63cb5f --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Horiz.php @@ -0,0 +1,70 @@ +<?php + +/** +* +* Parses for horizontal ruling lines. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Horiz.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for horizontal ruling lines. +* +* This class implements a Text_Wiki_Parse to find source text marked to +* be a horizontal rule, as defined by four dashed on their own line. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Horiz extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/^([-]{4,})$/m'; + + + /** + * + * Generates a replacement token for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token marking the horizontal rule. + * + */ + + function process(&$matches) + { + return $this->wiki->addToken($this->rule); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Html.php b/includes/pear/Text/Wiki/Parse/Default/Html.php new file mode 100644 index 0000000..38e7389 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Html.php @@ -0,0 +1,75 @@ +<?php + +/** +* +* Parses for blocks of HTML code. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Html.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for blocks of HTML code. +* +* This class implements a Text_Wiki_Parse to find source text marked as +* HTML to be redndred as-is. The block start is marked by <html> on its +* own line, and the block end is marked by </html> on its own line. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Html extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/^\<html\>\n(.+)\n\<\/html\>(\s|$)/Umsi'; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'text' => The text of the HTML to be rendered as-is. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token to be used as a placeholder in + * the source text, plus any text following the HTML block. + * + */ + + function process(&$matches) + { + $options = array('text' => $matches[1]); + return $this->wiki->addToken($this->rule, $options) . $matches[2]; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Image.php b/includes/pear/Text/Wiki/Parse/Default/Image.php new file mode 100644 index 0000000..5594644 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Image.php @@ -0,0 +1,136 @@ +<?php + +/** +* +* Parses for image placement. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Image.php 195859 2005-09-12 11:34:44Z toggg $ +* +*/ + +/** +* +* Parses for image placement. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Image extends Text_Wiki_Parse { + + /** + * URL schemes recognized by this rule. + * + * @access public + * @var array + */ + var $conf = array( + 'schemes' => 'http|https|ftp|gopher|news', + 'host_regexp' => '(?:[^.\s/"\'<\\\#delim#\ca-\cz]+\.)*[a-z](?:[-a-z0-9]*[a-z0-9])?\.?', + 'path_regexp' => '(?:/[^\s"<\\\#delim#\ca-\cz]*)?' + ); + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = '/(\[\[image\s+)(.+?)(\]\])/i'; + + + /** + * The regular expressions used to check ecternal urls + * + * @access public + * @var string + * @see parse() + */ + var $url = ''; + + /** + * Constructor. + * We override the constructor to build up the url regex from config + * + * @param object &$obj the base conversion handler + * @return The parser object + * @access public + */ + function Text_Wiki_Parse_Image(&$obj) + { + $default = $this->conf; + parent::Text_Wiki_Parse($obj); + + // convert the list of recognized schemes to a regex OR, + $schemes = $this->getConf('schemes', $default['schemes']); + $this->url = str_replace( '#delim#', $this->wiki->delim, + '#(?:' . (is_array($schemes) ? implode('|', $schemes) : $schemes) . ')://' + . $this->getConf('host_regexp', $default['host_regexp']) + . $this->getConf('path_regexp', $default['path_regexp']) .'#'); + } + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'src' => The image source, typically a relative path name. + * + * 'opts' => Any macro options following the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + $pos = strpos($matches[2], ' '); + + if ($pos === false) { + $options = array( + 'src' => $matches[2], + 'attr' => array()); + } else { + // everything after the space is attribute arguments + $options = array( + 'src' => substr($matches[2], 0, $pos), + 'attr' => $this->getAttrs(substr($matches[2], $pos+1)) + ); + // check the scheme case of external link + if (array_key_exists('link', $options['attr'])) { + // external url ? + if (($pos = strpos($options['attr']['link'], '://')) !== false) { + if (!preg_match($this->url, $options['attr']['link'])) { + return $matches[0]; + } + } elseif (in_array('Wikilink', $this->wiki->disable)) { + return $matches[0]; // Wikilink disabled + } + } + } + + return $this->wiki->addToken($this->rule, $options); + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Include.php b/includes/pear/Text/Wiki/Parse/Default/Include.php new file mode 100644 index 0000000..c86b15e --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Include.php @@ -0,0 +1,100 @@ +<?php + +/** +* +* Includes the contents of another PHP script into the source text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Include.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* This class implements a Text_Wiki_Parse to include the results of a +* script directly into the source at parse-time; thus, the output of the +* script will be parsed by Text_Wiki. This differs from the 'embed' +* rule, which incorporates the results at render-time, meaning that the +* 'embed' content is not parsed by Text_Wiki. +* +* DANGER! +* +* This rule is inherently not secure; it allows cross-site scripting to +* occur if the embedded output has <script> or other similar tags. Be +* careful. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Include extends Text_Wiki_Parse { + + var $conf = array( + 'base' => '/path/to/scripts/' + ); + + var $file = null; + + var $output = null; + + var $vars = null; + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = '/(\[\[include )(.+?)( .+?)?(\]\])/i'; + + + /** + * + * Includes the results of the script directly into the source; the output + * will subsequently be parsed by the remaining Text_Wiki rules. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return The results of the included script. + * + */ + + function process(&$matches) + { + // save the file location + $this->file = $this->getConf('base', './') . $matches[2]; + + // extract attribs as variables in the local space + $this->vars = $this->getAttrs($matches[3]); + unset($this->vars['this']); + extract($this->vars); + + // run the script + ob_start(); + include($this->file); + $this->output = ob_get_contents(); + ob_end_clean(); + + // done, place the script output directly in the source + return $this->output; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Interwiki.php b/includes/pear/Text/Wiki/Parse/Default/Interwiki.php new file mode 100644 index 0000000..e5f1d77 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Interwiki.php @@ -0,0 +1,138 @@ +<?php + +/** +* +* Parses for interwiki links. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Interwiki.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for interwiki links. +* +* This class implements a Text_Wiki_Parse to find source text marked as +* an Interwiki link. See the regex for a detailed explanation of the +* text matching procedure; e.g., "InterWikiName:PageName". +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Interwiki extends Text_Wiki_Parse { + + // double-colons wont trip up now + var $regex = '([A-Za-z0-9_]+):((?!:)[A-Za-z0-9_\/=&~#.:;-]+)'; + + + /** + * + * Parser. We override the standard parser so we can + * find both described interwiki links and standalone links. + * + * @access public + * + * @return void + * + */ + + function parse() + { + // described interwiki links + $tmp_regex = '/\[' . $this->regex . ' (.+?)\]/'; + $this->wiki->source = preg_replace_callback( + $tmp_regex, + array(&$this, 'processDescr'), + $this->wiki->source + ); + + // standalone interwiki links + $tmp_regex = '/' . $this->regex . '/'; + $this->wiki->source = preg_replace_callback( + $tmp_regex, + array(&$this, 'process'), + $this->wiki->source + ); + + } + + + /** + * + * Generates a replacement for the matched standalone interwiki text. + * Token options are: + * + * 'site' => The key name for the Text_Wiki interwiki array map, + * usually the name of the interwiki site. + * + * 'page' => The page on the target interwiki to link to. + * + * 'text' => The text to display as the link. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token to be used as a placeholder in + * the source text, plus any text priot to the match. + * + */ + + function process(&$matches) + { + $options = array( + 'site' => $matches[1], + 'page' => $matches[2], + 'text' => $matches[0] + ); + + return $this->wiki->addToken($this->rule, $options); + } + + + /** + * + * Generates a replacement for described interwiki links. Token + * options are: + * + * 'site' => The key name for the Text_Wiki interwiki array map, + * usually the name of the interwiki site. + * + * 'page' => The page on the target interwiki to link to. + * + * 'text' => The text to display as the link. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token to be used as a placeholder in + * the source text, plus any text priot to the match. + * + */ + + function processDescr(&$matches) + { + $options = array( + 'site' => $matches[1], + 'page' => $matches[2], + 'text' => $matches[3] + ); + + return $this->wiki->addToken($this->rule, $options); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Italic.php b/includes/pear/Text/Wiki/Parse/Default/Italic.php new file mode 100644 index 0000000..97ba9ff --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Italic.php @@ -0,0 +1,85 @@ +<?php + +/** +* +* Parses for italic text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Italic.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for italic text. +* +* This class implements a Text_Wiki_Parse to find source text marked for +* emphasis (italics) as defined by text surrounded by two single-quotes. +* On parsing, the text itself is left in place, but the starting and ending +* instances of two single-quotes are replaced with tokens. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Italic extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/''(()|[^'].*)''/U"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the text to be + * emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/List.php b/includes/pear/Text/Wiki/Parse/Default/List.php new file mode 100644 index 0000000..0e6d466 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/List.php @@ -0,0 +1,262 @@ +<?php + +/** +* +* Parses for bulleted and numbered lists. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: List.php 248434 2007-12-17 16:12:25Z justinpatrin $ +* +*/ + +/** +* +* Parses for bulleted and numbered lists. +* +* This class implements a Text_Wiki_Parse to find source text marked as +* a bulleted or numbered list. In short, if a line starts with '* ' then +* it is a bullet list item; if a line starts with '# ' then it is a +* number list item. Spaces in front of the * or # indicate an indented +* sub-list. The list items must be on sequential lines, and may be +* separated by blank lines to improve readability. Using a non-* non-# +* non-whitespace character at the beginning of a line ends the list. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_List extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/^((\*|#)\s.*\n)(?!\2\s|(?:\s+((?:\*|#) |\n)))/Usm'; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => + * 'bullet_start' : the start of a bullet list + * 'bullet_end' : the end of a bullet list + * 'number_start' : the start of a number list + * 'number_end' : the end of a number list + * 'item_start' : the start of item text (bullet or number) + * 'item_end' : the end of item text (bullet or number) + * 'unknown' : unknown type of list or item + * + * 'level' => the indent level (0 for the first level, 1 for the + * second, etc) + * + * 'count' => the list item number at this level. not needed for + * xhtml, but very useful for PDF and RTF. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A series of text and delimited tokens marking the different + * list text and list elements. + * + */ + + function process(&$matches) + { + // the replacement text we will return + $return = ''; + + // the list of post-processing matches + $list = array(); + + // a stack of list-start and list-end types; we keep this + // so that we know what kind of list we're working with + // (bullet or number) and what indent level we're at. + $stack = array(); + + // the item count is the number of list items for any + // given list-type on the stack + $itemcount = array(); + + // have we processed the very first list item? + $pastFirst = false; + + // populate $list with this set of matches. $matches[1] is the + // text matched as a list set by parse(). + preg_match_all( + '=^(\s*)(\*|#)\s(.*)$=Ums', + $matches[1], + $list, + PREG_SET_ORDER + ); + + $numSpaces = 0; + + // loop through each list-item element. + foreach ($list as $key => $val) { + + // $val[0] is the full matched list-item line + // $val[1] is the number of initial spaces (indent level) + // $val[2] is the list item type (* or #) + // $val[3] is the list item text + + // how many levels are we indented? (1 means the "root" + // list level, no indenting.) + $level = strlen($val[1]) + 1; + + // get the list item type + if ($val[2] == '*') { + $type = 'bullet'; + } elseif ($val[2] == '#') { + $type = 'number'; + } else { + $type = 'unknown'; + } + + // get the text of the list item + $text = $val[3]; + + // add a level to the list? + if ($level > count($stack)) { + + //watch for the same # of spaces and reset level + if ($level == $numSpaces) { + $level = count($stack); + } else { + + $numSpaces = $level; + + // reset level as sometimes people use too many spaces + $level = count($stack) + 1; + + // the current indent level is greater than the + // number of stack elements, so we must be starting + // a new list. push the new list type onto the + // stack... + array_push($stack, $type); + + // ...and add a list-start token to the return. + $return .= $this->wiki->addToken( + $this->rule, + array( + 'type' => $type . '_list_start', + 'level' => $level - 1 + ) + ); + } + } + + + // remove a level from the list? + while (count($stack) > $level) { + + // so we don't keep counting the stack, we set up a temp + // var for the count. -1 becuase we're going to pop the + // stack in the next command. $tmp will then equal the + // current level of indent. + $tmp = count($stack) - 1; + + // as long as the stack count is greater than the + // current indent level, we need to end list types. + // continue adding end-list tokens until the stack count + // and the indent level are the same. + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => array_pop($stack) . '_list_end', + 'level' => $tmp + ) + ); + + // reset to the current (previous) list type so that + // the new list item matches the proper list type. + $type = $stack[$tmp - 1]; + + // reset the item count for the popped indent level + unset($itemcount[$tmp + 1]); + } + + // add to the item count for this list (taking into account + // which level we are at). + if (! isset($itemcount[$level])) { + // first count + $itemcount[$level] = 0; + } else { + // increment count + $itemcount[$level]++; + } + + // is this the very first item in the list? + if (! $pastFirst) { + $first = true; + $pastFirst = true; + } else { + $first = false; + } + + // create a list-item starting token. + $start = $this->wiki->addToken( + $this->rule, + array( + 'type' => $type . '_item_start', + 'level' => $level, + 'count' => $itemcount[$level], + 'first' => $first + ) + ); + + // create a list-item ending token. + $end = $this->wiki->addToken( + $this->rule, + array( + 'type' => $type . '_item_end', + 'level' => $level, + 'count' => $itemcount[$level] + ) + ); + + // add the starting token, list-item text, and ending token + // to the return. + $return .= $start . $val[3] . $end; + } + + // the last list-item may have been indented. go through the + // list-type stack and create end-list tokens until the stack + // is empty. + while (count($stack) > 0) { + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => array_pop($stack) . '_list_end', + 'level' => count($stack) + ) + ); + } + + // we're done! send back the replacement text. + return "\n\n" . $return . "\n\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Newline.php b/includes/pear/Text/Wiki/Parse/Default/Newline.php new file mode 100644 index 0000000..495b7d7 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Newline.php @@ -0,0 +1,75 @@ +<?php + +/** +* +* Parses for implied line breaks indicated by newlines. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Newline.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for implied line breaks indicated by newlines. +* +* This class implements a Text_Wiki_Parse to mark implied line breaks in the +* source text, usually a single carriage return in the middle of a paragraph +* or block-quoted text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Newline extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/([^\n])\n([^\n])/m'; + + + /** + * + * Generates a replacement token for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A delimited token to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + return $matches[1] . + $this->wiki->addToken($this->rule) . + $matches[2]; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Paragraph.php b/includes/pear/Text/Wiki/Parse/Default/Paragraph.php new file mode 100644 index 0000000..e748a11 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Paragraph.php @@ -0,0 +1,116 @@ +<?php + +/** +* +* Parses for paragraph blocks. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Paragraph.php 286814 2009-08-04 17:03:17Z rodrigosprimo $ +* +*/ + +/** +* +* Parses for paragraph blocks. +* +* This class implements a Text_Wiki rule to find sections of the source +* text that are paragraphs. A para is any line not starting with a token +* delimiter, followed by two newlines. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Paragraph extends Text_Wiki_Parse { + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = "/^.*?\n\n/m"; + + var $conf = array( + 'skip' => array( + 'blockquote', // are we sure about this one? + 'code', + 'heading', + 'horiz', + 'deflist', + 'table', + 'list', + 'toc' + ) + ); + + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'start' => The starting point of the paragraph. + * + * 'end' => The ending point of the paragraph. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + $delim = $this->wiki->delim; + $skip = $this->getConf('skip', array()); + + // was anything there? + if (trim($matches[0]) == '') { + return ''; + } + + // does the match has tokens inside? + preg_match_all("/(?:$delim)(\d+?)(?:$delim)/", $matches[0], $delimiters, PREG_SET_ORDER); + + // look each delimiter inside the match and see if it's skippable + // (if we skip, it will not be marked as a paragraph) + foreach ($delimiters as $d) { + $token_type = strtolower($this->wiki->tokens[$d[1]][0]); + if (in_array($token_type, $skip)) { + return $matches[0]; + } + } + + // if there is no skipable token inside the match + // add the Paragraph token and return + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . trim($matches[0]) . $end; + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Phplookup.php b/includes/pear/Text/Wiki/Parse/Default/Phplookup.php new file mode 100644 index 0000000..7028ec1 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Phplookup.php @@ -0,0 +1,73 @@ +<?php + +/** +* +* Find source text marked for lookup in the PHP online manual. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Phplookup.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Find source text marked for lookup in the PHP online manual. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Phplookup extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\[\[php (.+?)\]\]/"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * teletype text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the teletype text. + * + */ + + function process(&$matches) + { + return $this->wiki->addToken( + $this->rule, array('text' => $matches[1]) + ); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Prefilter.php b/includes/pear/Text/Wiki/Parse/Default/Prefilter.php new file mode 100644 index 0000000..db20071 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Prefilter.php @@ -0,0 +1,84 @@ +<?php + +/** +* +* "Pre-filter" the source text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Prefilter.php 224599 2006-12-08 08:30:37Z justinpatrin $ +* +*/ + +/** +* +* "Pre-filter" the source text. +* +* Convert DOS and Mac line endings to Unix, concat lines ending in a +* backslash \ with the next line, convert tabs to 4-spaces, add newlines +* to the top and end of the source text, compress 3 or more newlines to +* 2 newlines. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Prefilter extends Text_Wiki_Parse { + + + /** + * + * Simple parsing method. + * + * @access public + * + */ + + function parse() + { + // convert DOS line endings + $this->wiki->source = str_replace("\r\n", "\n", + $this->wiki->source); + + // convert Macintosh line endings + $this->wiki->source = str_replace("\r", "\n", + $this->wiki->source); + + // concat lines ending in a backslash + $this->wiki->source = str_replace("\\\n", "", + $this->wiki->source); + + // convert tabs to four-spaces + $this->wiki->source = str_replace("\t", " ", + $this->wiki->source); + + // add extra newlines at the top and end; this + // seems to help many rules. + $this->wiki->source = "\n" . $this->wiki->source . "\n\n"; + + $this->wiki->source = str_replace("\n----\n","\n\n----\n\n", + $this->wiki->source); + $this->wiki->source = preg_replace("/\n(\\+{1,6})(.*)\n/m", + "\n\n\\1 \\2\n\n", + $this->wiki->source); + + // finally, compress all instances of 3 or more newlines + // down to two newlines. + $find = "/\n{3,}/m"; + $replace = "\n\n"; + $this->wiki->source = preg_replace($find, $replace, + $this->wiki->source); + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Raw.php b/includes/pear/Text/Wiki/Parse/Default/Raw.php new file mode 100644 index 0000000..c4f1b53 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Raw.php @@ -0,0 +1,73 @@ +<?php + +/** +* +* Parses for text marked as "raw" (i.e., to be rendered as-is). +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Raw.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for text marked as "raw" (i.e., to be rendered as-is). +* +* This class implements a Text_Wiki rule to find sections of the source +* text that are not to be processed by Text_Wiki. These blocks of "raw" +* text will be rendered as they were found. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Raw extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to find source text matching this + * rule. + * + * @access public + * + * @var string + * + */ + + var $regex = "/``(.*)``/U"; + + + /** + * + * Generates a token entry for the matched text. Token options are: + * + * 'text' => The full matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token number to be used as a placeholder in + * the source text. + * + */ + + function process(&$matches) + { + $options = array('text' => $matches[1]); + return $this->wiki->addToken($this->rule, $options); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Revise.php b/includes/pear/Text/Wiki/Parse/Default/Revise.php new file mode 100644 index 0000000..28bf9fd --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Revise.php @@ -0,0 +1,145 @@ +<?php + +/** +* +* Parses for text marked as revised (insert/delete). +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Revise.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for text marked as revised (insert/delete). +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Revise extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\@\@({*?.*}*?)\@\@/U"; + + + /** + * + * Config options. + * + * @access public + * + * @var array + * + */ + + var $conf = array( + 'delmark' => '---', + 'insmark' => '+++' + ); + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * inserted text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the teletype text. + * + */ + + function process(&$matches) + { + $output = ''; + $src = $matches[1]; + $delmark = $this->getConf('delmark'); // --- + $insmark = $this->getConf('insmark'); // +++ + + // '---' must be before '+++' (if they both appear) + $del = strpos($src, $delmark); + $ins = strpos($src, $insmark); + + // if neither is found, return right away + if ($del === false && $ins === false) { + return $matches[0]; + } + + // handle text to be deleted + if ($del !== false) { + + // move forward to the end of the deletion mark + $del += strlen($delmark); + + if ($ins === false) { + // there is no insertion text following + $text = substr($src, $del); + } else { + // there is insertion text following, + // mitigate the length + $text = substr($src, $del, $ins - $del); + } + + $output .= $this->wiki->addToken( + $this->rule, array('type' => 'del_start') + ); + + $output .= $text; + + $output .= $this->wiki->addToken( + $this->rule, array('type' => 'del_end') + ); + } + + // handle text to be inserted + if ($ins !== false) { + + // move forward to the end of the insert mark + $ins += strlen($insmark); + $text = substr($src, $ins); + + $output .= $this->wiki->addToken( + $this->rule, array('type' => 'ins_start') + ); + + $output .= $text; + + $output .= $this->wiki->addToken( + $this->rule, array('type' => 'ins_end') + ); + } + + return $output; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Smiley.php b/includes/pear/Text/Wiki/Parse/Default/Smiley.php new file mode 100644 index 0000000..358af6c --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Smiley.php @@ -0,0 +1,157 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Default: Parses for smileys / emoticons tags + * + * This class implements a Text_Wiki_Rule to find source text marked as + * smileys defined by symbols as ':)' , ':-)' or ':smile:' + * The symbol is replaced with a token. + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Smiley.php 197527 2005-10-04 08:17:51Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Smiley rule parser class for Default. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki_Parse::Text_Wiki_Parse() + */ +class Text_Wiki_Parse_Smiley extends Text_Wiki_Parse { + + /** + * Configuration keys for this rule + * 'smileys' => array Smileys recognized by this rule, symbols key definitions: + * 'symbol' => array ( 'name', 'description' [, 'variante', ...] ) as + * ':)' => array('smile', 'Smile'), + * ':D' => array('biggrin', 'Very Happy',':grin:'), + * the eventual elements after symbol and description are variantes + * + * 'auto_nose' => boolean enabling the auto nose feature: + * auto build a variante for 2 chars symbols by inserting a '-' as ':)' <=> ':-)' + * + * @access public + * @var array 'config-key' => mixed config-value + */ + var $conf = array( + 'smileys' => array( + ':D' => array('biggrin', 'Very Happy', ':grin:'), + ':)' => array('smile', 'Smile', '(:'), + ':(' => array('sad', 'Sad', '):'), + ':o' => array('surprised', 'Surprised', ':eek:', 'o:'), + ':shock:' => array('eek', 'Shocked'), + ':?' => array('confused', 'Confused', ':???:'), + '8)' => array('cool', 'Cool', '(8'), + ':lol:' => array('lol', 'Laughing'), + ':x' => array('mad', 'Mad'), + ':P' => array('razz', 'Razz'), + ':oops:' => array('redface', 'Embarassed'), + ':cry:' => array('cry', 'Crying or Very sad'), + ':evil:' => array('evil', 'Evil or Very Mad'), + ':twisted:' => array('twisted', 'Twisted Evil'), + ':roll:' => array('rolleyes', 'Rolling Eyes'), + ';)' => array('wink', 'Wink', '(;'), + ':!:' => array('exclaim', 'Exclamation'), + ':?:' => array('question', 'Question'), + ':idea:' => array('idea', 'Idea'), + ':arrow:' => array('arrow', 'Arrow'), + ':|' => array('neutral', 'Neutral', '|:'), + ':mrgreen:' => array('mrgreen', 'Mr. Green'), + ), + 'auto_nose' => true + ); + + /** + * Definition array of smileys, variantes references their model + * 'symbol' => array ( 'name', 'description') + * + * @access private + * @var array 'config-key' => mixed config-value + */ + var $_smileys = array(); + + /** + * Constructor. + * We override the constructor to build up the regex from config + * + * @param object &$obj the base conversion handler + * @return The parser object + * @access public + */ + function Text_Wiki_Parse_Smiley(&$obj) + { + $default = $this->conf; + parent::Text_Wiki_Parse($obj); + + // read the list of smileys to sort out variantes and :xxx: while building the regexp + $this->_smileys = $this->getConf('smileys', $default['smileys']); + $autoNose = $this->getConf('auto_nose', $default['auto_nose']); + $reg1 = $reg2 = ''; + $sep1 = ':(?:'; + $sep2 = ''; + foreach ($this->_smileys as $smiley => $def) { + for ($i = 1; $i < count($def); $i++) { + if ($i > 1) { + $cur = $def[$i]; + $this->_smileys[$cur] = &$this->_smileys[$smiley]; + } else { + $cur = $smiley; + } + $len = strlen($cur); + if (($cur{0} == ':') && ($len > 2) && ($cur{$len - 1} == ':')) { + $reg1 .= $sep1 . preg_quote(substr($cur, 1, -1), '#'); + $sep1 = '|'; + continue; + } + if ($autoNose && ($len === 2)) { + $variante = $cur{0} . '-' . $cur{1}; + $this->_smileys[$variante] = &$this->_smileys[$smiley]; + $cur = preg_quote($cur{0}, '#') . '-?' . preg_quote($cur{1}, '#'); + } else { + $cur = preg_quote($cur, '#'); + } + $reg2 .= $sep2 . $cur; + $sep2 = '|'; + } + } + $delim = '[\n\r\s' . $this->wiki->delim . '$^]'; + $this->regex = '#(?<=' . $delim . + ')(' . ($reg1 ? $reg1 . '):' . ($reg2 ? '|' : '') : '') . $reg2 . + ')(?=' . $delim . ')#i'; + } + + /** + * Generates a replacement token for the matched text. Token options are: + * 'symbol' => the original marker + * 'name' => the name of the smiley + * 'desc' => the description of the smiley + * + * @param array &$matches The array of matches from parse(). + * @return string Delimited token representing the smiley + * @access public + */ + function process(&$matches) + { + // tokenize + return $this->wiki->addToken($this->rule, + array( + 'symbol' => $matches[1], + 'name' => $this->_smileys[$matches[1]][0], + 'desc' => $this->_smileys[$matches[1]][1] + )); + } +} +?> diff --git a/includes/pear/Text/Wiki/Parse/Default/Strong.php b/includes/pear/Text/Wiki/Parse/Default/Strong.php new file mode 100644 index 0000000..3fc4595 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Strong.php @@ -0,0 +1,86 @@ +<?php + +/** +* +* Parses for strongly-emphasized text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Strong.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + + +/** +* +* Parses for strongly-emphasized text. +* +* This class implements a Text_Wiki_Parse to find source text marked for +* strong emphasis (bold) as defined by text surrounded by three +* single-quotes. On parsing, the text itself is left in place, but the +* starting and ending instances of three single-quotes are replaced with +* tokens. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Strong extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\*\*(.*?)\*\*/"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Subscript.php b/includes/pear/Text/Wiki/Parse/Default/Subscript.php new file mode 100644 index 0000000..eb75c14 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Subscript.php @@ -0,0 +1,79 @@ +<?php + +/** +* +* Parses for subscripted text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Subscript.php 180691 2005-02-24 17:24:56Z pmjones $ +* +*/ + +/** +* +* Parses for subscripted text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Subscript extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/,,(()|.*),,/U"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Superscript.php b/includes/pear/Text/Wiki/Parse/Default/Superscript.php new file mode 100644 index 0000000..e63c190 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Superscript.php @@ -0,0 +1,79 @@ +<?php + +/** +* +* Parses for superscripted text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Superscript.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for superscripted text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Superscript extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\^\^(()|.*)\^\^/U"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Table.php b/includes/pear/Text/Wiki/Parse/Default/Table.php new file mode 100644 index 0000000..81168b5 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Table.php @@ -0,0 +1,226 @@ +<?php + +/** +* +* Parses for table markup. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Table.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Parses for table markup. +* +* This class implements a Text_Wiki_Parse to find source text marked as a +* set of table rows, where a line start and ends with double-pipes (||) +* and uses double-pipes to separate table cells. The rows must be on +* sequential lines (no blank lines between them) -- a blank line +* indicates the beginning of a new table. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Table extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = '/\n((\|\|).*)(\n)(?!(\|\|))/Us'; + + + /** + * + * Generates a replacement for the matched text. + * + * Token options are: + * + * 'type' => + * 'table_start' : the start of a bullet list + * 'table_end' : the end of a bullet list + * 'row_start' : the start of a number list + * 'row_end' : the end of a number list + * 'cell_start' : the start of item text (bullet or number) + * 'cell_end' : the end of item text (bullet or number) + * + * 'cols' => the number of columns in the table (for 'table_start') + * + * 'rows' => the number of rows in the table (for 'table_start') + * + * 'span' => column span (for 'cell_start') + * + * 'attr' => column attribute flag (for 'cell_start') + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A series of text and delimited tokens marking the different + * table elements and cell text. + * + */ + + function process(&$matches) + { + // our eventual return value + $return = ''; + + // the number of columns in the table + $num_cols = 0; + + // the number of rows in the table + $num_rows = 0; + + // rows are separated by newlines in the matched text + $rows = explode("\n", $matches[1]); + + // loop through each row + foreach ($rows as $row) { + + // increase the row count + $num_rows ++; + + // start a new row + $return .= $this->wiki->addToken( + $this->rule, + array('type' => 'row_start') + ); + + // cells are separated by double-pipes + $cell = explode("||", $row); + + // get the number of cells (columns) in this row + $last = count($cell) - 1; + + // is this more than the current column count? + // (we decrease by 1 because we never use cell zero) + if ($last - 1 > $num_cols) { + // increase the column count + $num_cols = $last - 1; + } + + // by default, cells span only one column (their own) + $span = 1; + + // ignore cell zero, and ignore the "last" cell; cell zero + // is before the first double-pipe, and the "last" cell is + // after the last double-pipe. both are always empty. + for ($i = 1; $i < $last; $i ++) { + + // if there is no content at all, then it's an instance + // of two sets of || next to each other, indicating a + // span. + if ($cell[$i] == '') { + + // add to the span and loop to the next cell + $span += 1; + continue; + + } else { + + // this cell has content. + + // find any special "attr"ibute cell markers + if (substr($cell[$i], 0, 2) == '> ') { + // right-align + $attr = 'right'; + $cell[$i] = substr($cell[$i], 2); + } elseif (substr($cell[$i], 0, 2) == '= ') { + // center-align + $attr = 'center'; + $cell[$i] = substr($cell[$i], 2); + } elseif (substr($cell[$i], 0, 2) == '< ') { + // left-align + $attr = 'left'; + $cell[$i] = substr($cell[$i], 2); + } elseif (substr($cell[$i], 0, 2) == '~ ') { + $attr = 'header'; + $cell[$i] = substr($cell[$i], 2); + } else { + $attr = null; + } + + // start a new cell... + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'cell_start', + 'attr' => $attr, + 'span' => $span + ) + ); + + // ...add the content... + $return .= trim($cell[$i]); + + // ...and end the cell. + $return .= $this->wiki->addToken( + $this->rule, + array ( + 'type' => 'cell_end', + 'attr' => $attr, + 'span' => $span + ) + ); + + // reset the span. + $span = 1; + } + + } + + // end the row + $return .= $this->wiki->addToken( + $this->rule, + array('type' => 'row_end') + ); + + } + + // wrap the return value in start and end tokens + $return = + $this->wiki->addToken( + $this->rule, + array( + 'type' => 'table_start', + 'rows' => $num_rows, + 'cols' => $num_cols + ) + ) + . $return . + $this->wiki->addToken( + $this->rule, + array( + 'type' => 'table_end' + ) + ); + + // we're done! + return "\n$return\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Tighten.php b/includes/pear/Text/Wiki/Parse/Default/Tighten.php new file mode 100644 index 0000000..a03b51f --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Tighten.php @@ -0,0 +1,49 @@ +<?php + +/** +* +* The rule removes all remaining newlines. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Tighten.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + + +/** +* +* The rule removes all remaining newlines. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Tighten extends Text_Wiki_Parse { + + + /** + * + * Apply tightening directly to the source text. + * + * @access public + * + */ + + function parse() + { + $this->wiki->source = str_replace("\n", '', + $this->wiki->source); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Toc.php b/includes/pear/Text/Wiki/Parse/Default/Toc.php new file mode 100644 index 0000000..dd29a57 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Toc.php @@ -0,0 +1,130 @@ +<?php + +/** +* +* Looks through parsed text and builds a table of contents. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Toc.php 187179 2005-05-28 21:33:02Z pmjones $ +* +*/ + +/** +* +* Looks through parsed text and builds a table of contents. +* +* This class implements a Text_Wiki_Parse to find all heading tokens and +* build a table of contents. The [[toc]] tag gets replaced with a list +* of all the level-2 through level-6 headings. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + + +class Text_Wiki_Parse_Toc extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/\n\[\[toc( .*)?\]\]\n/m"; + + + /** + * + * Generates a replacement for the matched text. + * + * Token options are: + * + * 'type' => ['list_start'|'list_end'|'item_start'|'item_end'|'target'] + * + * 'level' => The heading level (1-6). + * + * 'count' => Which entry number this is in the list. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A token indicating the TOC collection point. + * + */ + + function process(&$matches) + { + $count = 0; + + if (isset($matches[1])) { + $attr = $this->getAttrs(trim($matches[1])); + } else { + $attr = array(); + } + + $output = $this->wiki->addToken( + $this->rule, + array( + 'type' => 'list_start', + 'level' => 0, + 'attr' => $attr + ) + ); + + foreach ($this->wiki->getTokens('Heading') as $key => $val) { + + if ($val[1]['type'] != 'start') { + continue; + } + + $options = array( + 'type' => 'item_start', + 'id' => $val[1]['id'], + 'level' => $val[1]['level'], + 'count' => $count ++ + ); + + $output .= $this->wiki->addToken($this->rule, $options); + + $output .= $val[1]['text']; + + $output .= $this->wiki->addToken( + $this->rule, + array( + 'type' => 'item_end', + 'level' => $val[1]['level'] + ) + ); + } + + $output .= $this->wiki->addToken( + $this->rule, array( + 'type' => 'list_end', + 'level' => 0 + ) + ); + + return "\n$output\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Tt.php b/includes/pear/Text/Wiki/Parse/Default/Tt.php new file mode 100644 index 0000000..6fb7a7f --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Tt.php @@ -0,0 +1,84 @@ +<?php + +/** +* +* Find source text marked for teletype (monospace). +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Tt.php 180591 2005-02-23 17:38:29Z pmjones $ +* +*/ + +/** +* +* Find source text marked for teletype (monospace). +* +* Defined by text surrounded by two curly braces. On parsing, the text +* itself is left in place, but the starting and ending instances of +* curly braces are replaced with tokens. +* +* Token options are: +* +* 'type' => ['start'|'end'] The starting or ending point of the +* teletype text. The text itself is left in the source. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Tt extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/{{({*?.*}*?)}}/U"; + + + /** + * + * Generates a replacement for the matched text. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return string A pair of delimited tokens to be used as a + * placeholder in the source text surrounding the teletype text. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken( + $this->rule, array('type' => 'start') + ); + + $end = $this->wiki->addToken( + $this->rule, array('type' => 'end') + ); + + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Underline.php b/includes/pear/Text/Wiki/Parse/Default/Underline.php new file mode 100644 index 0000000..dc04de4 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Underline.php @@ -0,0 +1,79 @@ +<?php + +/** +* +* Parses for bold text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Underline.php 190446 2005-07-10 20:40:20Z justinpatrin $ +* +*/ + +/** +* +* Parses for bold text. +* +* This class implements a Text_Wiki_Rule to find source text marked for +* strong emphasis (bold) as defined by text surrounded by three +* single-quotes. On parsing, the text itself is left in place, but the +* starting and ending instances of three single-quotes are replaced with +* tokens. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Underline extends Text_Wiki_Parse { + + + /** + * + * The regular expression used to parse the source text and find + * matches conforming to this rule. Used by the parse() method. + * + * @access public + * + * @var string + * + * @see parse() + * + */ + + var $regex = "/__(()|[^_].*)__/U"; + + + /** + * + * Generates a replacement for the matched text. Token options are: + * + * 'type' => ['start'|'end'] The starting or ending point of the + * emphasized text. The text itself is left in the source. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A pair of delimited tokens to be used as a placeholder in + * the source text surrounding the text to be emphasized. + * + */ + + function process(&$matches) + { + $start = $this->wiki->addToken($this->rule, array('type' => 'start')); + $end = $this->wiki->addToken($this->rule, array('type' => 'end')); + return $start . $matches[1] . $end; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Url.php b/includes/pear/Text/Wiki/Parse/Default/Url.php new file mode 100644 index 0000000..7a6f08c --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Url.php @@ -0,0 +1,281 @@ +<?php + +/** +* +* Parse for URLS in the source text. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Url.php 293784 2010-01-20 18:48:09Z justinpatrin $ +* +*/ + +/** +* +* Parse for URLS in the source text. +* +* Various URL markings are supported: inline (the URL by itself), +* numbered or footnote reference (where the URL is enclosed in square +* brackets), and named reference (where the URL is enclosed in square +* brackets and has a name included inside the brackets). E.g.: +* +* inline -- http://example.com +* numbered -- [http://example.com] +* described -- [http://example.com Example Description] +* +* When rendering a URL token, this will convert URLs pointing to a .gif, +* .jpg, or .png image into an inline <img /> tag (for the 'xhtml' +* format). +* +* Token options are: +* +* 'type' => ['inline'|'footnote'|'descr'] the type of URL +* +* 'href' => the URL link href portion +* +* 'text' => the displayed text of the URL link +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Url extends Text_Wiki_Parse { + + + /** + * + * Keeps a running count of numbered-reference URLs. + * + * @access public + * + * @var int + * + */ + + var $footnoteCount = 0; + + + /** + * + * URL schemes recognized by this rule. + * + * @access public + * + * @var array + * + */ + + var $conf = array( + 'schemes' => array( + 'http://', + 'https://', + 'ftp://', + 'gopher://', + 'news://', + 'mailto:' + ) + ); + + + /** + * + * Constructor. + * + * We override the constructor so we can comment the regex nicely. + * + * @access public + * + */ + + function Text_Wiki_Parse_Url(&$obj) + { + parent::Text_Wiki_Parse($obj); + + // convert the list of recognized schemes to a regex-safe string, + // where the pattern delim is a slash + $tmp = array(); + $list = $this->getConf('schemes', array()); + foreach ($list as $val) { + $tmp[] = preg_quote($val, '/'); + } + $schemes = implode('|', $tmp); + + // build the regex + $this->regex = + "($schemes)" . // allowed schemes + "(" . // start pattern + "[^ \\/\"\'{$this->wiki->delim}]*\\/" . // no spaces, backslashes, slashes, double-quotes, single quotes, or delimiters; + ")*" . // end pattern + "[^ \\t\\n\\/\"\'{$this->wiki->delim}]*" . + "[A-Za-z0-9\\/?=&~_#]"; + } + + + /** + * + * Find three different kinds of URLs in the source text. + * + * @access public + * + */ + + function parse() + { + // ------------------------------------------------------------- + // + // Described-reference (named) URLs. + // + + // the regular expression for this kind of URL + $tmp_regex = '/\[(' . $this->regex . ') ([^\]]+)\]/'; + + // use a custom callback processing method to generate + // the replacement text for matches. + $this->wiki->source = preg_replace_callback( + $tmp_regex, + array(&$this, 'processDescr'), + $this->wiki->source + ); + + + // ------------------------------------------------------------- + // + // Numbered-reference (footnote-style) URLs. + // + + // the regular expression for this kind of URL + $tmp_regex = '/\[(' . $this->regex . ')\]/U'; + + // use a custom callback processing method to generate + // the replacement text for matches. + $this->wiki->source = preg_replace_callback( + $tmp_regex, + array(&$this, 'processFootnote'), + $this->wiki->source + ); + + + // ------------------------------------------------------------- + // + // Normal inline URLs. + // + + // the regular expression for this kind of URL + + $tmp_regex = '/(^|[^A-Za-z])(' . $this->regex . ')(.*?)/'; + + // use the standard callback for inline URLs + $this->wiki->source = preg_replace_callback( + $tmp_regex, + array(&$this, 'process'), + $this->wiki->source + ); + } + + + /** + * + * Process inline URLs. + * + * @param array &$matches + * + * @param array $matches An array of matches from the parse() method + * as generated by preg_replace_callback. $matches[0] is the full + * matched string, $matches[1] is the first matched pattern, + * $matches[2] is the second matched pattern, and so on. + * + * @return string The processed text replacement. + * + */ + + function process(&$matches) + { + // set options + $options = array( + 'type' => 'inline', + 'href' => $matches[2], + 'text' => $matches[2] + ); + + // tokenize + return $matches[1] . $this->wiki->addToken($this->rule, $options) . $matches[5]; + } + + + /** + * + * Process numbered (footnote) URLs. + * + * Token options are: + * @param array &$matches + * + * @param array $matches An array of matches from the parse() method + * as generated by preg_replace_callback. $matches[0] is the full + * matched string, $matches[1] is the first matched pattern, + * $matches[2] is the second matched pattern, and so on. + * + * @return string The processed text replacement. + * + */ + + function processFootnote(&$matches) + { + // keep a running count for footnotes + $this->footnoteCount++; + + // set options + $options = array( + 'type' => 'footnote', + 'href' => $matches[1], + 'text' => $this->footnoteCount + ); + + // tokenize + return $this->wiki->addToken($this->rule, $options); + } + + + /** + * + * Process described-reference (named-reference) URLs. + * + * Token options are: + * 'type' => ['inline'|'footnote'|'descr'] the type of URL + * 'href' => the URL link href portion + * 'text' => the displayed text of the URL link + * + * @param array &$matches + * + * @param array $matches An array of matches from the parse() method + * as generated by preg_replace_callback. $matches[0] is the full + * matched string, $matches[1] is the first matched pattern, + * $matches[2] is the second matched pattern, and so on. + * + * @return string The processed text replacement. + * + */ + + function processDescr(&$matches) + { + // set options + $options = array( + 'type' => 'descr', + 'href' => $matches[1], + 'text' => $matches[4] + ); + + // tokenize + return $this->wiki->addToken($this->rule, $options); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Parse/Default/Wikilink.php b/includes/pear/Text/Wiki/Parse/Default/Wikilink.php new file mode 100644 index 0000000..c3d81c3 --- /dev/null +++ b/includes/pear/Text/Wiki/Parse/Default/Wikilink.php @@ -0,0 +1,204 @@ +<?php + +/** +* +* Parse for links to wiki pages. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +* @license LGPL +* +* @version $Id: Wikilink.php 224598 2006-12-08 08:23:51Z justinpatrin $ +* +*/ + +/** +* +* Parse for links to wiki pages. +* +* Wiki page names are typically in StudlyCapsStyle made of +* WordsSmashedTogether. +* +* You can also create described links to pages in this style: +* [WikiPageName nice text link to use for display] +* +* The token options for this rule are: +* +* 'page' => the wiki page name. +* +* 'text' => the displayed link text. +* +* 'anchor' => a named anchor on the target wiki page. +* +* @category Text +* +* @package Text_Wiki +* +* @author Paul M. Jones <pmjones@php.net> +* +*/ + +class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse { + + var $conf = array ( + 'ext_chars' => false, + 'utf-8' => false + ); + + /** + * + * Constructor. + * + * We override the Text_Wiki_Parse constructor so we can + * explicitly comment each part of the $regex property. + * + * @access public + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + */ + + function Text_Wiki_Parse_Wikilink(&$obj) + { + parent::Text_Wiki_Parse($obj); + + if ($this->getConf('utf-8')) { + $upper = 'A-Z\p{Lu}'; + $lower = 'a-z0-9\p{Ll}'; + $either = 'A-Za-z0-9\p{L}'; + } else if ($this->getConf('ext_chars')) { + // use an extended character set; this should + // allow for umlauts and so on. taken from the + // Tavi project defaults.php file. + $upper = 'A-Z\xc0-\xde'; + $lower = 'a-z0-9\xdf-\xfe'; + $either = 'A-Za-z0-9\xc0-\xfe'; + } else { + // the default character set, should be fine + // for most purposes. + $upper = "A-Z"; + $lower = "a-z0-9"; + $either = "A-Za-z0-9"; + } + + // build the regular expression for finding WikiPage names. + $this->regex = + "(!?" . // START WikiPage pattern (1) + "[$upper]" . // 1 upper + "[$either]*" . // 0+ alpha or digit + "[$lower]+" . // 1+ lower or digit + "[$upper]" . // 1 upper + "[$either]*" . // 0+ or more alpha or digit + ")" . // END WikiPage pattern (/1) + "((\#" . // START Anchor pattern (2)(3) + "[$either]" . // 1 alpha + "(" . // start sub pattern (4) + "[-_$either:.]*" . // 0+ dash, alpha, digit, underscore, colon, dot + "[-_$either]" . // 1 dash, alpha, digit, or underscore + ")?)?)"; // end subpatterns (/4)(/3)(/2) + } + + + /** + * + * First parses for described links, then for standalone links. + * + * @access public + * + * @return void + * + */ + + function parse() + { + // described wiki links + $tmp_regex = '/\[' . $this->regex . ' (.+?)\]/'.($this->getConf('utf-8') ? 'u' : ''); + $this->wiki->source = preg_replace_callback( + $tmp_regex, + array(&$this, 'processDescr'), + $this->wiki->source + ); + + // standalone wiki links + if ($this->getConf('utf-8')) { + $either = 'A-Za-z0-9\p{L}'; + } else if ($this->getConf('ext_chars')) { + $either = "A-Za-z0-9\xc0-\xfe"; + } else { + $either = "A-Za-z0-9"; + } + + $tmp_regex = "/(^|[^{$either}\-_]){$this->regex}/".($this->getConf('utf-8') ? 'u' : ''); + $this->wiki->source = preg_replace_callback( + $tmp_regex, + array(&$this, 'process'), + $this->wiki->source + ); + } + + + /** + * + * Generate a replacement for described links. + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token to be used as a placeholder in + * the source text, plus any text priot to the match. + * + */ + + function processDescr(&$matches) + { + // set the options + $options = array( + 'page' => $matches[1], + 'text' => $matches[5], + 'anchor' => $matches[3] + ); + + // create and return the replacement token and preceding text + return $this->wiki->addToken($this->rule, $options); // . $matches[7]; + } + + + /** + * + * Generate a replacement for standalone links. + * + * + * @access public + * + * @param array &$matches The array of matches from parse(). + * + * @return A delimited token to be used as a placeholder in + * the source text, plus any text prior to the match. + * + */ + + function process(&$matches) + { + // when prefixed with !, it's explicitly not a wiki link. + // return everything as it was. + if ($matches[2]{0} == '!') { + return $matches[1] . substr($matches[2], 1) . $matches[3]; + } + + // set the options + $options = array( + 'page' => $matches[2], + 'text' => $matches[2] . $matches[3], + 'anchor' => $matches[3] + ); + + // create and return the replacement token and preceding text + return $matches[1] . $this->wiki->addToken($this->rule, $options); + } +} +?> diff --git a/includes/pear/Text/Wiki/Render.php b/includes/pear/Text/Wiki/Render.php new file mode 100644 index 0000000..5bc26f7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render.php @@ -0,0 +1,218 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Base rendering class for parsed and tokenized text. + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Render.php 209118 2006-03-11 07:12:13Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Base rendering class for parsed and tokenized text. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render { + + + /** + * + * Configuration options for this render rule. + * + * @access public + * + * @var string + * + */ + + var $conf = array(); + + + /** + * + * The name of this rule's format. + * + * @access public + * + * @var string + * + */ + + var $format = null; + + + /** + * + * The name of this rule's token array elements. + * + * @access public + * + * @var string + * + */ + + var $rule = null; + + + /** + * + * A reference to the calling Text_Wiki object. + * + * This is needed so that each rule has access to the same source + * text, token set, URLs, interwiki maps, page names, etc. + * + * @access public + * + * @var object + */ + + var $wiki = null; + + + /** + * + * Constructor for this render format or rule. + * + * @access public + * + * @param object &$obj The calling "parent" Text_Wiki object. + * + */ + + function Text_Wiki_Render(&$obj) + { + // keep a reference to the calling Text_Wiki object + $this->wiki =& $obj; + + // get the config-key-name for this object, + // strip the Text_Wiki_Render_ part + // 01234567890123456 + $tmp = get_class($this); + $tmp = substr($tmp, 17); + + // split into pieces at the _ mark. + // first part is format, second part is rule. + $part = explode('_', $tmp); + $this->format = isset($part[0]) ? ucwords(strtolower($part[0])) : null; + $this->rule = isset($part[1]) ? ucwords(strtolower($part[1])) : null; + + // is there a format but no rule? + // then this is the "main" render object, with + // pre() and post() methods. + if ($this->format && ! $this->rule && + isset($this->wiki->formatConf[$this->format]) && + is_array($this->wiki->formatConf[$this->format])) { + + // this is a format render object + $this->conf = array_merge( + $this->conf, + $this->wiki->formatConf[$this->format] + ); + + } + + // is there a format and a rule? + if ($this->format && $this->rule && + isset($this->wiki->renderConf[$this->format][$this->rule]) && + is_array($this->wiki->renderConf[$this->format][$this->rule])) { + + // this is a rule render object + $this->conf = array_merge( + $this->conf, + $this->wiki->renderConf[$this->format][$this->rule] + ); + } + } + + + /** + * + * Simple method to safely get configuration key values. + * + * @access public + * + * @param string $key The configuration key. + * + * @param mixed $default If the key does not exist, return this value + * instead. + * + * @return mixed The configuration key value (if it exists) or the + * default value (if not). + * + */ + + function getConf($key, $default = null) + { + if (isset($this->conf[$key])) { + return $this->conf[$key]; + } else { + return $default; + } + } + + + /** + * + * Simple method to wrap a configuration in an sprintf() format. + * + * @access public + * + * @param string $key The configuration key. + * + * @param string $format The sprintf() format string. + * + * @return mixed The formatted configuration key value (if it exists) + * or null (if it does not). + * + */ + + function formatConf($format, $key) + { + if (isset($this->conf[$key])) { + //$this->conf[$key] needs a textEncode....at least for Xhtml output... + return sprintf($format, $this->conf[$key]); + } else { + return null; + } + } + + /** + * Default method to render url + * + * @access public + * @param string $urlChunk a part of an url to render + * @return rendered url + * + */ + + function urlEncode($urlChunk) + { + return rawurlencode($urlChunk); + } + + /** + * Default method to render text (htmlspecialchars) + * + * @access public + * @param string $text the text to render + * @return rendered text + * + */ + + function textEncode($text) + { + return htmlspecialchars($text); + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Creole.php b/includes/pear/Text/Wiki/Render/Creole.php new file mode 100644 index 0000000..4eb9a5e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole.php @@ -0,0 +1,16 @@ +<?php + +class Text_Wiki_Render_Creole extends Text_Wiki_Render { + + function pre() + { + return; + } + + function post() + { + return; + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Address.php b/includes/pear/Text/Wiki/Render/Creole/Address.php new file mode 100644 index 0000000..21726a6 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Address.php @@ -0,0 +1,29 @@ +<?php + +class Text_Wiki_Render_Creole_Address extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return "-- "; + } + + if ($options['type'] == 'end') { + return "\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Anchor.php b/includes/pear/Text/Wiki/Render/Creole/Anchor.php new file mode 100644 index 0000000..59dd668 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Anchor.php @@ -0,0 +1,23 @@ +<?php + +/** +* +* This class renders an anchor target name in Creole. +* +* @author Manuel Holtgrewe <purestorm at ggnore dot net> +* +* @author Paul M. Jones <pmjones at ciaweb dot net> +* +* @package Text_Wiki +* +*/ + +class Text_Wiki_Render_Creole_Anchor extends Text_Wiki_Render { + + function token($options) + { + return ''; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Blockquote.php b/includes/pear/Text/Wiki/Render/Creole/Blockquote.php new file mode 100644 index 0000000..f6a3f06 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Blockquote.php @@ -0,0 +1,47 @@ +<?php + +class Text_Wiki_Render_Creole_Blockquote extends Text_Wiki_Render { + + var $css_stack = array(); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // starting + if ($options['type'] == 'start') { + if (empty($options['css'])) $options['css'] = ''; + array_push($this->css_stack, $options['css']); + $this->wiki->registerRenderCallback(array(&$this, 'renderInsideText')); + return ''; + } + // ending + if ($options['type'] == 'end') { + $this->wiki->popRenderCallback(); + return ''; + } + } + + function renderInsideText($text) { + $text = trim($text); + if (array_pop($this->css_stack) == 'remark') { + $text = preg_replace('/(^|\n)([\>\:]*) */', '\1:\2 ', $text); + } + else { + $text = preg_replace('/(^|\n)([\>\:]*) */', '\1>\2 ', $text); + } + return $text . "\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Bold.php b/includes/pear/Text/Wiki/Render/Creole/Bold.php new file mode 100644 index 0000000..fac63c9 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Bold.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Bold extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return '**'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Box.php b/includes/pear/Text/Wiki/Render/Creole/Box.php new file mode 100644 index 0000000..c977c25 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Box.php @@ -0,0 +1,30 @@ +<?php + +//There is no box rule for Creole, so use a simple table +class Text_Wiki_Render_Creole_Box extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return "|| "; + } + + if ($options['type'] == 'end') { + return "\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Break.php b/includes/pear/Text/Wiki/Render/Creole/Break.php new file mode 100644 index 0000000..375d53a --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Break.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Creole_Break extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\\\\"; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Center.php b/includes/pear/Text/Wiki/Render/Creole/Center.php new file mode 100644 index 0000000..bed9c5e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Center.php @@ -0,0 +1,30 @@ +<?php + +//There is no center rule for Creole, so use a simple table +class Text_Wiki_Render_Creole_Center extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return "| "; + } + + if ($options['type'] == 'end') { + return "\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Code.php b/includes/pear/Text/Wiki/Render/Creole/Code.php new file mode 100644 index 0000000..a31bea5 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Code.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Code extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "{{{\n" . $options['text'] . "\n}}}\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Colortext.php b/includes/pear/Text/Wiki/Render/Creole/Colortext.php new file mode 100644 index 0000000..19091c6 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Colortext.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Colortext extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Deflist.php b/includes/pear/Text/Wiki/Render/Creole/Deflist.php new file mode 100644 index 0000000..05f47d0 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Deflist.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Deflist extends Text_Wiki_Render { + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\n"; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Delimiter.php b/includes/pear/Text/Wiki/Render/Creole/Delimiter.php new file mode 100644 index 0000000..900448c --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Delimiter.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Delimiter extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Embed.php b/includes/pear/Text/Wiki/Render/Creole/Embed.php new file mode 100644 index 0000000..dcf2cc1 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Embed.php @@ -0,0 +1,29 @@ +<?php + +class Text_Wiki_Render_Creole_Embed extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return "{{"; + } + + if ($options['type'] == 'end') { + return "}}"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Emphasis.php b/includes/pear/Text/Wiki/Render/Creole/Emphasis.php new file mode 100644 index 0000000..ddc9237 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Emphasis.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Emphasis extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return '//'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Freelink.php b/includes/pear/Text/Wiki/Render/Creole/Freelink.php new file mode 100644 index 0000000..0439ac5 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Freelink.php @@ -0,0 +1,9 @@ +<?php + +require_once 'Text/Wiki/Render/Creole/Wikilink.php'; + +class Text_Wiki_Render_Creole_Freelink extends Text_Wiki_Render_Creole_Wikilink { + // renders identically to wikilinks, only the parsing is different :-) +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Function.php b/includes/pear/Text/Wiki/Render/Creole/Function.php new file mode 100644 index 0000000..0f07917 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Function.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Function extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Heading.php b/includes/pear/Text/Wiki/Render/Creole/Heading.php new file mode 100644 index 0000000..94ebf84 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Heading.php @@ -0,0 +1,16 @@ +<?php + +class Text_Wiki_Render_Creole_Heading extends Text_Wiki_Render { + function token($options) + { + if ($options['type'] == 'start') { + return str_pad('', $options['level'], '=') . ' '; + } + else if ($options['type'] == 'end') { + // next line would add trailing '=' signs + // return ' ' . str_pad('', $options['level'], '=') . "\n\n"; + return "\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Horiz.php b/includes/pear/Text/Wiki/Render/Creole/Horiz.php new file mode 100644 index 0000000..2937beb --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Horiz.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Horiz extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "----\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Html.php b/includes/pear/Text/Wiki/Render/Creole/Html.php new file mode 100644 index 0000000..f76a90e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Html.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Html extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "{{{" . $options['text'] . "}}}"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Image.php b/includes/pear/Text/Wiki/Render/Creole/Image.php new file mode 100644 index 0000000..1c4476f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Image.php @@ -0,0 +1,26 @@ +<?php +class Text_Wiki_Render_Creole_Image extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if (!strlen($options['attr']['alt']) || $options['src'] == $options['attr']['alt']) { + return '{{'.$options['src'].'}}'; + } else { + return '{{'.$options['src'].'|'.$options['attr']['alt'].'}}'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Include.php b/includes/pear/Text/Wiki/Render/Creole/Include.php new file mode 100644 index 0000000..a0d30cf --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Include.php @@ -0,0 +1,16 @@ +<?php + +class Text_Wiki_Render_Creole_Include extends Text_Wiki_Render { + + function token() + { + if ($options['type'] == 'start') { + return "{{"; + } + + if ($options['type'] == 'end') { + return "}}"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Interwiki.php b/includes/pear/Text/Wiki/Render/Creole/Interwiki.php new file mode 100644 index 0000000..d97eec4 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Interwiki.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Interwiki extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return '[['.$options['site'].'>'.$options['page'].(strlen($options['text']) ? '|'.$options['text'] : '').']]'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Italic.php b/includes/pear/Text/Wiki/Render/Creole/Italic.php new file mode 100644 index 0000000..6d8d0a0 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Italic.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Italic extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return '//'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/List.php b/includes/pear/Text/Wiki/Render/Creole/List.php new file mode 100644 index 0000000..bbb9b76 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/List.php @@ -0,0 +1,53 @@ +<?php + +class Text_Wiki_Render_Creole_List extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * This rendering method is syntactically and semantically compliant + * with XHTML 1.1 in that sub-lists are part of the previous list item. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variables (type, level, count) + + switch ($options['type']) { + + case 'bullet_list_start': + case 'number_list_start': + return ''; + break; + case 'bullet_list_end': + case 'number_list_end': + if ($options['level'] == 0) { + return "\n"; + } + break; + case 'bullet_item_start': + $pad = str_pad('', $options['level'], '*'); + return $pad . ' '; + break; + case 'number_item_start': + $pad = str_pad('', $options['level'], '#'); + return $pad . ' '; + break; + case 'bullet_item_end': + case 'number_item_end': + default: + return "\n"; + break; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Newline.php b/includes/pear/Text/Wiki/Render/Creole/Newline.php new file mode 100644 index 0000000..697d8eb --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Newline.php @@ -0,0 +1,12 @@ +<?php + +class Text_Wiki_Render_Creole_Newline extends Text_Wiki_Render { + + + function token($options) + { + return "\n"; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Paragraph.php b/includes/pear/Text/Wiki/Render/Creole/Paragraph.php new file mode 100644 index 0000000..7d3840d --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Paragraph.php @@ -0,0 +1,29 @@ +<?php + +class Text_Wiki_Render_Creole_Paragraph extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return ""; + } + + if ($options['type'] == 'end') { + return "\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Phplookup.php b/includes/pear/Text/Wiki/Render/Creole/Phplookup.php new file mode 100644 index 0000000..b667238 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Phplookup.php @@ -0,0 +1,25 @@ +<?php + +// $Id: Phplookup.php 222265 2006-10-23 13:11:27Z mic $ + +class Text_Wiki_Render_Creole_Phplookup extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Prefilter.php b/includes/pear/Text/Wiki/Render/Creole/Prefilter.php new file mode 100644 index 0000000..d997af0 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Prefilter.php @@ -0,0 +1,10 @@ +<?php + +class Text_Wiki_Render_Creole_Prefilter extends Text_Wiki_Render { + function token() + { + return ''; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Preformatted.php b/includes/pear/Text/Wiki/Render/Creole/Preformatted.php new file mode 100644 index 0000000..4d0796f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Preformatted.php @@ -0,0 +1,27 @@ +<?php + +class Text_Wiki_Render_Creole_Preformatted extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = $options['text']; + + $text = preg_replace("/\n( +)}}}/s", "\n$1 }}}", $text); + + return "{{{\n" . $text . "\n}}}\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Raw.php b/includes/pear/Text/Wiki/Render/Creole/Raw.php new file mode 100644 index 0000000..7fd8b19 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Raw.php @@ -0,0 +1,33 @@ +<?php + +class Text_Wiki_Render_Creole_Raw extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = $options['text']; + if ($text == '\\') $text = ' '; + if (isset($options['type']) && $options['type'] == 'escape') { + $text = '~' . $text; + } + else { + $find = "/}}}(?!})/"; + $replace = "}}}}}}{{{"; + $text = preg_replace($find, $replace, $text); + } + return $text; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Revise.php b/includes/pear/Text/Wiki/Render/Creole/Revise.php new file mode 100644 index 0000000..1497c42 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Revise.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Revise extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Strong.php b/includes/pear/Text/Wiki/Render/Creole/Strong.php new file mode 100644 index 0000000..de7da41 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Strong.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Strong extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return '**'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Subscript.php b/includes/pear/Text/Wiki/Render/Creole/Subscript.php new file mode 100644 index 0000000..640cfb7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Subscript.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Subscript extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ',,'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Superscript.php b/includes/pear/Text/Wiki/Render/Creole/Superscript.php new file mode 100644 index 0000000..c9dd995 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Superscript.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Superscript extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return '^^'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Table.php b/includes/pear/Text/Wiki/Render/Creole/Table.php new file mode 100644 index 0000000..e24ea37 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Table.php @@ -0,0 +1,70 @@ +<?php + +class Text_Wiki_Render_Creole_Table extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + + + switch ($options['type']) { + + case 'table_start': + return ''; + break; + + case 'table_end': + return "\n"; + break; + + case 'caption_start': + return '|= '; + break; + + case 'caption_end': + return "\n"; + break; + + case 'row_start': + return ''; + break; + + case 'row_end': + return "\n"; + break; + + case 'cell_start': + // is this a TH or TD cell? + if ($options['attr'] == 'header') { + // start a header cell + $output = '|= '; + } else { + // start a normal cell + $output = '| '; + } + return $output; + break; + + case 'cell_end': + return ' '; + break; + + default: + return ''; + + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Tighten.php b/includes/pear/Text/Wiki/Render/Creole/Tighten.php new file mode 100644 index 0000000..67200d0 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Tighten.php @@ -0,0 +1,10 @@ +<?php + +class Text_Wiki_Render_Creole_Tighten extends Text_Wiki_Render { + + function token() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Toc.php b/includes/pear/Text/Wiki/Render/Creole/Toc.php new file mode 100644 index 0000000..715292d --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Toc.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Toc extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Tt.php b/includes/pear/Text/Wiki/Render/Creole/Tt.php new file mode 100644 index 0000000..8dbc371 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Tt.php @@ -0,0 +1,29 @@ +<?php + +class Text_Wiki_Render_Creole_Tt extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return "{{{"; + } + + if ($options['type'] == 'end') { + return "}}}"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Underline.php b/includes/pear/Text/Wiki/Render/Creole/Underline.php new file mode 100644 index 0000000..e239555 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Underline.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Creole_Underline extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return '__'; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Url.php b/includes/pear/Text/Wiki/Render/Creole/Url.php new file mode 100644 index 0000000..ca4ed2f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Url.php @@ -0,0 +1,40 @@ +<?php + +class Text_Wiki_Render_Creole_Url extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + extract($options); + if ($type == 'start') { + return '[['.$href.'|'; + } + else if ($type == 'end') { + return ']]'; + } + else { + $noprot = str_replace('http://', '', str_replace('mailto:', '', $href)); + if (strpos($href, "#ref") === 0 || strpos($href, "#fn") === 0) { + return $text; + } + else if (! strlen($text) || $text == $href || $text == $noprot) { + return '[['.$href.']]'; + } else { + return '[['.$href.'|'.$text.']]'; + } + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Creole/Wikilink.php b/includes/pear/Text/Wiki/Render/Creole/Wikilink.php new file mode 100644 index 0000000..fe4fc37 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Creole/Wikilink.php @@ -0,0 +1,43 @@ +<?php + +class Text_Wiki_Render_Creole_Wikilink extends Text_Wiki_Render { + + /** + * + * Renders a token into XHTML. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $dup = (($options['page'] == $options['text']) || ($options['page'] == preg_replace('/\s+/', '_', $options['text']))); + + if ($options['type'] == 'start') { + if ($dup) return '[['; + else return '[['.$options['page']. + (strlen($options['anchor']) ? $options['anchor'] : ''). + (strlen($options['text']) && (strlen($options['page']) || strlen($options['anchor'])) ? '|' : ''); + } else if ($options['type'] == 'end') { + if ($dup && strlen($options['anchor'])) return $options['anchor'].']]'; + else return ']]'; + } else { + if ($dup) return '[['. + (strlen($options['text']) ? $options['text'] : ''). + (strlen($options['anchor']) ? $options['anchor'] : ''). + ']]'; + else return '[['.$options['page']. + (strlen($options['anchor']) ? $options['anchor'] : ''). + (strlen($options['text']) && strlen($options['page']) && strlen($options['anchor']) ? '|' : ''). + (strlen($options['text']) ? $options['text'] : ''). + ']]'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex.php b/includes/pear/Text/Wiki/Render/Latex.php new file mode 100644 index 0000000..7eca39e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex.php @@ -0,0 +1,90 @@ +<?php + +/** +* +* Formats parsed Text_Wiki for LaTeX rendering. +* +* $Id: Latex.php 169211 2004-09-25 19:05:14Z pmjones $ +* +* @author Jeremy Cowgar <jeremy@cowgar.com> +* +* @package Text_Wiki +* +* @todo [http://google.com] becomes 1 with a LaTeX footnote in subscript. +* This should be a normal LaTeX footnote associated with the +* previous word? +* +* @todo parse "..." to be ``...'' +* +* @todo parse '...' to be `...' +* +* @todo move escape_latex to a static function, move escaping to the +* individual .php files they are associated with +* +* @todo allow the user to add conf items to do things like +* + A custom document header +* + Custom page headings +* + Include packages +* + Set Title, Author, Date +* + Include a title page +* + Not output Document Head/Foot (maybe combinding many pages?) +* +*/ + +class Text_Wiki_Render_Latex extends Text_Wiki_Render { + function escape_latex ($txt) { + $txt = str_replace("\\", "\\\\", $txt); + $txt = str_replace('#', '\#', $txt); + $txt = str_replace('$', '\$', $txt); + $txt = str_replace('%', '\%', $txt); + $txt = str_replace('^', '\^', $txt); + $txt = str_replace('&', '\&', $txt); + $txt = str_replace('_', '\_', $txt); + $txt = str_replace('{', '\{', $txt); + $txt = str_replace('}', '\}', $txt); + + // Typeset things a bit prettier than normas + $txt = str_replace('~', '$\sim$', $txt); + $txt = str_replace('...', '\ldots', $txt); + + return $txt; + } + + function escape($tok, $ele) { + if (isset($tok[$ele])) { + $tok[$ele] = $this->escape_latex($tok[$ele]); + } + + return $tok; + } + + function pre() + { + foreach ($this->wiki->tokens as $k => $tok) { + if ($tok[0] == 'Code') { + continue; + } + + $tok[1] = $this->escape($tok[1], 'text'); + $tok[1] = $this->escape($tok[1], 'page'); + $tok[1] = $this->escape($tok[1], 'href'); + + $this->wiki->tokens[$k] = $tok; + } + + $this->wiki->source = $this->escape_latex($this->wiki->source); + + return + "\\documentclass{article}\n". + "\\usepackage{ulem}\n". + "\\pagestyle{headings}\n". + "\\begin{document}\n"; + } + + function post() + { + return "\\end{document}\n"; + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Anchor.php b/includes/pear/Text/Wiki/Render/Latex/Anchor.php new file mode 100644 index 0000000..fcf487f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Anchor.php @@ -0,0 +1,33 @@ +<?php + +/** +* +* This class renders an anchor target name in LaTeX. +* +* $Id: Anchor.php 169211 2004-09-25 19:05:14Z pmjones $ +* +* @author Jeremy Cowgar <jeremy@cowgar.com> +* +* @package Text_Wiki +* +*/ + +class Text_Wiki_Render_Latex_Anchor extends Text_Wiki_Render { + + function token($options) + { + extract($options); // $type, $name + + if ($type == 'start') { + //return sprintf('<a id="%s">',$name); + return ''; + } + + if ($type == 'end') { + //return '</a>'; + return ''; + } + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Blockquote.php b/includes/pear/Text/Wiki/Render/Latex/Blockquote.php new file mode 100644 index 0000000..9810622 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Blockquote.php @@ -0,0 +1,36 @@ +<?php + +class Text_Wiki_Render_Latex_Blockquote extends Text_Wiki_Render { + + var $conf = array('css' => null); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $type = $options['type']; + $level = $options['level']; + + // starting + if ($type == 'start') { + return "\\begin{quote}\n"; + } + + // ending + if ($type == 'end') { + return "\\end{quote}\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Bold.php b/includes/pear/Text/Wiki/Render/Latex/Bold.php new file mode 100644 index 0000000..16427a9 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Bold.php @@ -0,0 +1,28 @@ +<?php +class Text_Wiki_Render_Latex_Bold extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\textbf{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Box.php b/includes/pear/Text/Wiki/Render/Latex/Box.php new file mode 100644 index 0000000..fff4331 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Box.php @@ -0,0 +1,54 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Box rule end renderer for Latex + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Box.php 193489 2005-08-15 09:50:57Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a box drawn in Latex. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Latex_Box extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\framebox[\textwidth]{'; + } + + if ($options['type'] == 'end') { + return "}\n"; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Break.php b/includes/pear/Text/Wiki/Render/Latex/Break.php new file mode 100644 index 0000000..1f3fdd3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Break.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Latex_Break extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\\newline\n"; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Center.php b/includes/pear/Text/Wiki/Render/Latex/Center.php new file mode 100644 index 0000000..59adffd --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Center.php @@ -0,0 +1,33 @@ +<?php + +class Text_Wiki_Render_Latex_Center extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return 'Center: NI'; + + if ($options['type'] == 'start') { + //return "\n<center>\n"; + return '<div style="text-align: center;">'; + } + + if ($options['type'] == 'end') { + //return "</center>\n"; + return '</div>'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Code.php b/includes/pear/Text/Wiki/Render/Latex/Code.php new file mode 100644 index 0000000..40bb45a --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Code.php @@ -0,0 +1,26 @@ +<?php + +class Text_Wiki_Render_Latex_Code extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = $options['text']; + + return "\\begin{verbatim}\n$text\n\\end{verbatim}\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Colortext.php b/includes/pear/Text/Wiki/Render/Latex/Colortext.php new file mode 100644 index 0000000..86ff095 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Colortext.php @@ -0,0 +1,58 @@ +<?php + +class Text_Wiki_Render_Latex_Colortext extends Text_Wiki_Render { + + var $colors = array( + 'aqua', + 'black', + 'blue', + 'fuchsia', + 'gray', + 'green', + 'lime', + 'maroon', + 'navy', + 'olive', + 'purple', + 'red', + 'silver', + 'teal', + 'white', + 'yellow' + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return 'Colortext: NI'; + + $type = $options['type']; + $color = $options['color']; + + if (! in_array($color, $this->colors)) { + $color = '#' . $color; + } + + if ($type == 'start') { + return "<span style=\"color: $color;\">"; + } + + if ($options['type'] == 'end') { + return '</span>'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Deflist.php b/includes/pear/Text/Wiki/Render/Latex/Deflist.php new file mode 100644 index 0000000..8dbfe97 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Deflist.php @@ -0,0 +1,53 @@ +<?php + +class Text_Wiki_Render_Latex_Deflist extends Text_Wiki_Render { + + var $conf = array( + 'css_dl' => null, + 'css_dt' => null, + 'css_dd' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $type = $options['type']; + switch ($type) + { + case 'list_start': + return "\\begin{description}\n"; + + case 'list_end': + return "\\end{description}\n\n"; + + case 'term_start': + return '\item['; + + case 'term_end': + return '] '; + + case 'narr_start': + return '{'; + + case 'narr_end': + return "}\n"; + + default: + return ''; + + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Delimiter.php b/includes/pear/Text/Wiki/Render/Latex/Delimiter.php new file mode 100644 index 0000000..49a50aa --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Delimiter.php @@ -0,0 +1,25 @@ +<?php + +class Text_Wiki_Render_Latex_Delimiter extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // TODO: Is this where I can do some LaTeX escaping for items + // such as $ { } _ ? + return "Delimiter: ".$options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Embed.php b/includes/pear/Text/Wiki/Render/Latex/Embed.php new file mode 100644 index 0000000..0936c7d --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Embed.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Latex_Embed extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "Embed: ".$options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Emphasis.php b/includes/pear/Text/Wiki/Render/Latex/Emphasis.php new file mode 100644 index 0000000..d949b42 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Emphasis.php @@ -0,0 +1,29 @@ +<?php + +class Text_Wiki_Render_Latex_Emphasis extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\textsl{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Font.php b/includes/pear/Text/Wiki/Render/Latex/Font.php new file mode 100644 index 0000000..7009f52 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Font.php @@ -0,0 +1,73 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * BBCode: extra Font rules renderer to size the text + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Font.php 209123 2006-03-11 11:14:23Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Font rule render class (used for BBCode) + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki::Text_Wiki_Render() + */ +class Text_Wiki_Render_Latex_Font extends Text_Wiki_Render { + + /** + * A table to translate the sizes + * + * @access public + * @var array + */ + var $sizes = array( + 'tiny' => 5, + 'scriptsize' => 7, + 'footnotesize' => 8, + 'small' => 9, + 'normalsize' => 11, + 'large' => 13, + 'Large' => 16, + 'LARGE' => 19, + 'huge' => 22, + 'Huge' => 9999); + + /** + * Renders a token into text matching the requested format. + * process the font size option + * + * @access public + * @param array $options The "options" portion of the token (second element). + * @return string The text rendered from the token options. + */ + function token($options) + { + if ($options['type'] == 'start') { + foreach ($this->sizes as $key => $lim) { + if ($options['size'] < $lim) { + break; + } + } + return '\{' . $key . '}{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Freelink.php b/includes/pear/Text/Wiki/Render/Latex/Freelink.php new file mode 100644 index 0000000..fdeae54 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Freelink.php @@ -0,0 +1,6 @@ +<?php + +class Text_Wiki_Render_Latex_Freelink extends Text_Wiki_Render_Latex_Wikilink { + // renders identically to wikilinks, only the parsing is different :-) +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Function.php b/includes/pear/Text/Wiki/Render/Latex/Function.php new file mode 100644 index 0000000..5f4c488 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Function.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Latex_Function extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "Function: NI"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Heading.php b/includes/pear/Text/Wiki/Render/Latex/Heading.php new file mode 100644 index 0000000..eab4d54 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Heading.php @@ -0,0 +1,33 @@ +<?php + +class Text_Wiki_Render_Latex_Heading extends Text_Wiki_Render { + + function token($options) + { + // get nice variable names (type, level) + extract($options); + + if ($type == 'start') { + switch ($level) + { + case '1': + return '\part{'; + case '2': + return '\section{'; + case '3': + return '\subsection{'; + case '4': + return '\subsubsection{'; + case '5': + return '\paragraph{'; + case '6': + return '\subparagraph{'; + } + } + + if ($type == 'end') { + return "}\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Horiz.php b/includes/pear/Text/Wiki/Render/Latex/Horiz.php new file mode 100644 index 0000000..bb159d4 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Horiz.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Latex_Horiz extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\n\\noindent\\rule{\\textwidth}{1pt}\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Html.php b/includes/pear/Text/Wiki/Render/Latex/Html.php new file mode 100644 index 0000000..350de4a --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Html.php @@ -0,0 +1,25 @@ +<?php + +class Text_Wiki_Render_Latex_Html extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + print_r($this); + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Image.php b/includes/pear/Text/Wiki/Render/Latex/Image.php new file mode 100644 index 0000000..e6c084b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Image.php @@ -0,0 +1,70 @@ +<?php +class Text_Wiki_Render_Latex_Image extends Text_Wiki_Render { + + var $conf = array( + 'base' => '/' + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return 'Image: NI'; + + $src = '"' . + $this->getConf('base', '/') . + $options['src'] . '"'; + + if (isset($options['attr']['link'])) { + + // this image has a link + if (strpos($options['attr']['link'], '://')) { + // it's a URL + $href = $options['attr']['link']; + } else { + $href = $this->wiki->getRenderConf('xhtml', 'wikilink', 'view_url') . + $options['attr']['link']; + } + + } else { + // image is not linked + $href = null; + } + + // unset these so they don't show up as attributes + unset($options['attr']['link']); + + $attr = ''; + $alt = false; + foreach ($options['attr'] as $key => $val) { + if (strtolower($key) == 'alt') { + $alt = true; + } + $attr .= " $key=\"$val\""; + } + + // always add an "alt" attribute per Stephane Solliec + if (! $alt) { + $attr .= ' alt="' . basename($options['src']) . '"'; + } + + if ($href) { + return "<a href=\"$href\"><img src=$src$attr/></a>"; + } else { + return "<img src=$src$attr/>"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Include.php b/includes/pear/Text/Wiki/Render/Latex/Include.php new file mode 100644 index 0000000..f23d7a5 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Include.php @@ -0,0 +1,8 @@ +<?php +class Text_Wiki_Render_Latex_Include extends Text_Wiki_Render { + function token() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Interwiki.php b/includes/pear/Text/Wiki/Render/Latex/Interwiki.php new file mode 100644 index 0000000..8f1da63 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Interwiki.php @@ -0,0 +1,58 @@ +<?php + +class Text_Wiki_Render_Latex_Interwiki extends Text_Wiki_Render { + + var $conf = array( + 'sites' => array( + 'MeatBall' => 'http://www.usemod.com/cgi-bin/mb.pl?%s', + 'Advogato' => 'http://advogato.org/%s', + 'Wiki' => 'http://c2.com/cgi/wiki?%s' + ) + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = $options['text']; + if (isset($options['url'])) { + // calculated by the parser (e.g. Mediawiki) + $href = $options['url']; + } else { + $site = $options['site']; + // toggg 2006/02/05 page name must be url encoded (e.g. may contain spaces) + $page = $this->urlEncode($options['page']); + + if (isset($this->conf['sites'][$site])) { + $href = $this->conf['sites'][$site]; + } else { + return $text; + } + + // old form where page is at end, + // or new form with %s placeholder for sprintf()? + if (strpos($href, '%s') === false) { + // use the old form + $href = $href . $page; + } else { + // use the new form + $href = sprintf($href, $page); + } + } + + return $text . '\footnote{' . $href . '}'; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Italic.php b/includes/pear/Text/Wiki/Render/Latex/Italic.php new file mode 100644 index 0000000..2b18888 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Italic.php @@ -0,0 +1,28 @@ +<?php +class Text_Wiki_Render_Latex_Italic extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\textit{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/List.php b/includes/pear/Text/Wiki/Render/Latex/List.php new file mode 100644 index 0000000..ff76aed --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/List.php @@ -0,0 +1,76 @@ +<?php + + +class Text_Wiki_Render_Latex_List extends Text_Wiki_Render { + /** + * + * Renders a token into text matching the requested format. + * + * This rendering method is syntactically and semantically compliant + * with XHTML 1.1 in that sub-lists are part of the previous list item. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variables (type, level, count) + extract($options); + + switch ($type) + { + case 'bullet_list_start': + return "\\begin{itemize}\n"; + + case 'bullet_list_end': + return "\\end{itemize}\n"; + + case 'number_list_start': + $depth = 'enumi' . str_pad('', $level, 'i'); + $enum = '\arabic'; + if (isset($format)) { + switch ($format) { + case 'a': + $enum = '\alph'; + break; + case 'A': + $enum = '\Alph'; + break; + case 'i': + $enum = '\roman'; + break; + case 'I': + $enum = '\Roman'; + break; + } + } + return '\renewcommand{\labelenumi}{' . $enum . '{' . $depth . + "}}\n\\begin{enumerate}\n"; + + case 'number_list_end': + return "\\end{enumerate}\n"; + + case 'bullet_item_start': + case 'number_item_start': + return '\item{'; + + case 'bullet_item_end': + case 'number_item_end': + return "}\n"; + + default: + // ignore item endings and all other types. + // item endings are taken care of by the other types + // depending on their place in the list. + return ''; + break; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Newline.php b/includes/pear/Text/Wiki/Render/Latex/Newline.php new file mode 100644 index 0000000..b1b918a --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Newline.php @@ -0,0 +1,12 @@ +<?php + +class Text_Wiki_Render_Latex_Newline extends Text_Wiki_Render { + + + function token($options) + { + return "\\newline\n"; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Page.php b/includes/pear/Text/Wiki/Render/Latex/Page.php new file mode 100644 index 0000000..41fc708 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Page.php @@ -0,0 +1,48 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Page rule end renderer for Latex + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Page.php 193514 2005-08-15 15:27:19Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders page markers in Latex. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Latex_Page extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\\newpage\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Paragraph.php b/includes/pear/Text/Wiki/Render/Latex/Paragraph.php new file mode 100644 index 0000000..45d487e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Paragraph.php @@ -0,0 +1,31 @@ +<?php + +class Text_Wiki_Render_Latex_Paragraph extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + extract($options); //type + + if ($type == 'start') { + return ''; + } + + if ($type == 'end') { + return "\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Phplookup.php b/includes/pear/Text/Wiki/Render/Latex/Phplookup.php new file mode 100644 index 0000000..8c75604 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Phplookup.php @@ -0,0 +1,34 @@ +<?php + +class Text_Wiki_Render_Latex_Phplookup extends Text_Wiki_Render { + + var $conf = array('target' => '_blank'); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return 'Phplookup: NI'; + + $text = trim($options['text']); + + $target = $this->getConf('target', ''); + if ($target) { + $target = " target=\"$target\""; + } + + return "<a$target href=\"http://php.net/$text\">$text</a>"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Plugin.php b/includes/pear/Text/Wiki/Render/Latex/Plugin.php new file mode 100644 index 0000000..220b101 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Plugin.php @@ -0,0 +1,49 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Plugin rule end renderer for Latex + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Plugin.php 193730 2005-08-17 09:16:36Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders wiki plugins in Latex. (empty) + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Latex_Plugin extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * Plugins produce wiki markup so are processed by parsing, no tokens produced + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Prefilter.php b/includes/pear/Text/Wiki/Render/Latex/Prefilter.php new file mode 100644 index 0000000..e833cd9 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Prefilter.php @@ -0,0 +1,56 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * This class implements a Text_Wiki_Render_Xhtml to "pre-filter" source text so + * that line endings are consistently \n, lines ending in a backslash \ + * are concatenated with the next line, and tabs are converted to spaces. + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Jeremy Cowgar <jeremy@cowgar.com> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Prefilter.php 248435 2007-12-17 16:19:44Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +// +----------------------------------------------------------------------+ +// | PHP version 4 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2003 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 2.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available through the world-wide-web at | +// | http://www.php.net/license/2_02.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Authors: | +// +----------------------------------------------------------------------+ +// +// $Id: Prefilter.php 248435 2007-12-17 16:19:44Z justinpatrin $ + + +/** +* +* This class implements a Text_Wiki_Render_Latex to "pre-filter" source text so +* that line endings are consistently \n, lines ending in a backslash \ +* are concatenated with the next line, and tabs are converted to spaces. +* +* @author Jeremy Cowgar <jeremy@cowgar.com> +* +* @package Text_Wiki +* +*/ + +class Text_Wiki_Render_Latex_Prefilter extends Text_Wiki_Render { + function token() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Preformatted.php b/includes/pear/Text/Wiki/Render/Latex/Preformatted.php new file mode 100644 index 0000000..25216b9 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Preformatted.php @@ -0,0 +1,48 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Preformatted rule end renderer for Latex + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Preformatted.php 193477 2005-08-15 06:15:41Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders preformated text in Latex. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Latex_Preformatted extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\\begin{verbatim}\n" . $options['text'] . "\n\\end{verbatim}\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Raw.php b/includes/pear/Text/Wiki/Render/Latex/Raw.php new file mode 100644 index 0000000..4ef4cbe --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Raw.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Latex_Raw extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "Raw: ".$options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Revise.php b/includes/pear/Text/Wiki/Render/Latex/Revise.php new file mode 100644 index 0000000..f880156 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Revise.php @@ -0,0 +1,38 @@ +<?php + +class Text_Wiki_Render_Latex_Revise extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'del_start') { + return '\sout{'; + } + + if ($options['type'] == 'del_end') { + return '}'; + } + + if ($options['type'] == 'ins_start') { + return '\underline{'; + } + + if ($options['type'] == 'ins_end') { + return '}'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Smiley.php b/includes/pear/Text/Wiki/Render/Latex/Smiley.php new file mode 100644 index 0000000..c049cc3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Smiley.php @@ -0,0 +1,44 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Smiley rule Latex renderer + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Smiley.php 192951 2005-08-10 11:42:08Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Smiley rule Latex render class + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki::Text_Wiki_Render() + */ +class Text_Wiki_Render_Latex_Smiley extends Text_Wiki_Render { + + /** + * Renders a token into text matching the requested format. + * process the Smileys + * + * @access public + * @param array $options The "options" portion of the token (second element). + * @return string The text rendered from the token options. + */ + function token($options) + { + return $options['symbol']; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Specialchar.php b/includes/pear/Text/Wiki/Render/Latex/Specialchar.php new file mode 100644 index 0000000..23358af --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Specialchar.php @@ -0,0 +1,54 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Specialchar rule end renderer for Latex + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Specialchar.php 193499 2005-08-15 11:10:38Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders special characters in Latex. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Latex_SpecialChar extends Text_Wiki_Render { + + var $types = array('~bs~' => '\\\\', + '~hs~' => '\hspace{1em}', + '~amp~' => '\&', + '~ldq~' => '``', + '~rdq~' => "''", + '~lsq~' => '`', + '~rsq~' => "'", + '~c~' => '\copyright', + '~--~' => '---', + '" -- "' => '---', + '" -- "' => '---', + '~lt~' => '<', + '~gt~' => '>'); + + function token($options) + { + if (isset($this->types[$options['char']])) { + return $this->types[$options['char']]; + } else { + return $options['char']; + } + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Strong.php b/includes/pear/Text/Wiki/Render/Latex/Strong.php new file mode 100644 index 0000000..721ffa3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Strong.php @@ -0,0 +1,30 @@ +<?php + +class Text_Wiki_Render_Latex_Strong extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\textbf{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Subscript.php b/includes/pear/Text/Wiki/Render/Latex/Subscript.php new file mode 100644 index 0000000..f265308 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Subscript.php @@ -0,0 +1,54 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Subscript rule end renderer for Latex + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Subscript.php 193490 2005-08-15 10:09:06Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders subscript text in Latex. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Latex_Subscript extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '_{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Superscript.php b/includes/pear/Text/Wiki/Render/Latex/Superscript.php new file mode 100644 index 0000000..887a8b3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Superscript.php @@ -0,0 +1,31 @@ +<?php + +class Text_Wiki_Render_Latex_Superscript extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return 'Superscript: NI'; + + if ($options['type'] == 'start') { + return '<sup>'; + } + + if ($options['type'] == 'end') { + return '</sup>'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Table.php b/includes/pear/Text/Wiki/Render/Latex/Table.php new file mode 100644 index 0000000..ad7866f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Table.php @@ -0,0 +1,99 @@ +<?php + +class Text_Wiki_Render_Latex_Table extends Text_Wiki_Render { + var $cell_id = 0; + var $cell_count = 0; + var $is_spanning = false; + + var $conf = array( + 'css_table' => null, + 'css_tr' => null, + 'css_th' => null, + 'css_td' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variable names (type, attr, span) + extract($options); + + switch ($type) + { + case 'table_start': + $this->cell_count = $cols; + + $tbl_start = '\begin{tabular}{|'; + for ($a=0; $a < $this->cell_count; $a++) { + $tbl_start .= 'l|'; + } + $tbl_start .= "}\n"; + + return $tbl_start; + + case 'table_end': + return "\\hline\n\\end{tabular}\n"; + + case 'caption_start': + return "\\caption{"; + + case 'caption_end': + return "}\n"; + + case 'row_start': + $this->is_spanning = false; + $this->cell_id = 0; + return "\\hline\n"; + + case 'row_end': + return "\\\\\n"; + + case 'cell_start': + if ($span > 1) { + $col_spec = ''; + if ($this->cell_id == 0) { + $col_spec = '|'; + } + $col_spec .= 'l|'; + + $this->cell_id += $span; + $this->is_spanning = true; + + return '\multicolumn{' . $span . '}{' . $col_spec . '}{'; + } + + $this->cell_id += 1; + return ''; + + case 'cell_end': + $out = ''; + if ($this->is_spanning) { + $this->is_spanning = false; + $out = '}'; + } + + if ($this->cell_id != $this->cell_count) { + $out .= ' & '; + } + + return $out; + + default: + return ''; + + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Tighten.php b/includes/pear/Text/Wiki/Render/Latex/Tighten.php new file mode 100644 index 0000000..7755e94 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Tighten.php @@ -0,0 +1,9 @@ +<?php +class Text_Wiki_Render_Latex_Tighten extends Text_Wiki_Render { + + function token() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Titlebar.php b/includes/pear/Text/Wiki/Render/Latex/Titlebar.php new file mode 100644 index 0000000..e58bda2 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Titlebar.php @@ -0,0 +1,54 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Titlebar rule end renderer for Latex + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Titlebar.php 193500 2005-08-15 11:36:55Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a title bar in Latex. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Latex_Titlebar extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\framebox[\textwidth]{\textbf{'; + } + + if ($options['type'] == 'end') { + return "}}\n"; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Toc.php b/includes/pear/Text/Wiki/Render/Latex/Toc.php new file mode 100644 index 0000000..fc5846f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Toc.php @@ -0,0 +1,30 @@ +<?php + +class Text_Wiki_Render_Latex_Toc extends Text_Wiki_Render { + + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if($options['type'] == 'list_start') { + return "\\tableofcontents\n\n"; + } + + return ''; + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Tt.php b/includes/pear/Text/Wiki/Render/Latex/Tt.php new file mode 100644 index 0000000..45073a7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Tt.php @@ -0,0 +1,30 @@ +<?php + +class Text_Wiki_Render_Latex_tt extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\texttt{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Underline.php b/includes/pear/Text/Wiki/Render/Latex/Underline.php new file mode 100644 index 0000000..81ed323 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Underline.php @@ -0,0 +1,30 @@ +<?php + +class Text_Wiki_Render_Latex_Underline extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '\underline{'; + } + + if ($options['type'] == 'end') { + return '}'; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Latex/Url.php b/includes/pear/Text/Wiki/Render/Latex/Url.php new file mode 100644 index 0000000..c81f9e6 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Url.php @@ -0,0 +1,41 @@ +<?php + + +class Text_Wiki_Render_Latex_Url extends Text_Wiki_Render { + + + var $conf = array( + 'target' => false, + 'images' => true, + 'img_ext' => array('jpg', 'jpeg', 'gif', 'png') + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // create local variables from the options array (text, + // href, type) + extract($options); + + if ($options['type'] == 'start') { + return ''; + } else if ($options['type'] == 'end') { + return '\footnote{' . $href . '}'; + } else { + return $text . '\footnote{' . $href . '}'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Latex/Wikilink.php b/includes/pear/Text/Wiki/Render/Latex/Wikilink.php new file mode 100644 index 0000000..5b4cdba --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Latex/Wikilink.php @@ -0,0 +1,60 @@ +<?php + +class Text_Wiki_Render_Latex_Wikilink extends Text_Wiki_Render { + var $conf = array( + 'pages' => array(), + 'view_url' => 'http://example.com/index.php?page=%s', + 'new_url' => 'http://example.com/new.php?page=%s', + 'new_text' => '?' + ); + + /** + * + * Renders a token into XHTML. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variable names (page, anchor, text) + extract($options); + + // are we checking page existence? + $list = $this->getConf('pages'); + if (is_array($list)) { + // yes, check against the page list + $exists = in_array($page, $list); + } else { + // no, assume it exists + $exists = true; + } + + // convert *after* checking against page names so as not to mess + // up what the user typed and what we're checking. + $page = $this->textEncode($page); + $anchor = $this->textEncode($anchor); + $text = $this->textEncode($text); + + $href = $this->getConf('view_url'); + + if (strpos($href, '%s') === false) { + // use the old form (page-at-end) + $href = $href . $page . $anchor; + } else { + // use the new form (sprintf format string) + $href = sprintf($href, $page . $anchor); + } + + // get the CSS class and generate output + $css = $this->formatConf(' class="%s"', 'css'); + return $text . '\footnote{' . $href . '}'; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain.php b/includes/pear/Text/Wiki/Render/Plain.php new file mode 100644 index 0000000..e1b02a8 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain.php @@ -0,0 +1,16 @@ +<?php + +class Text_Wiki_Render_Plain extends Text_Wiki_Render { + + function pre() + { + return; + } + + function post() + { + return; + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Anchor.php b/includes/pear/Text/Wiki/Render/Plain/Anchor.php new file mode 100644 index 0000000..f24f467 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Anchor.php @@ -0,0 +1,23 @@ +<?php + +/** +* +* This class renders an anchor target name in XHTML. +* +* @author Manuel Holtgrewe <purestorm at ggnore dot net> +* +* @author Paul M. Jones <pmjones at ciaweb dot net> +* +* @package Text_Wiki +* +*/ + +class Text_Wiki_Render_Plain_Anchor extends Text_Wiki_Render { + + function token($options) + { + return $options['name']; + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Blockquote.php b/includes/pear/Text/Wiki/Render/Plain/Blockquote.php new file mode 100644 index 0000000..08da94a --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Blockquote.php @@ -0,0 +1,39 @@ +<?php + +class Text_Wiki_Render_Plain_Blockquote extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $type = $options['type']; + $level = $options['level']; + + // set up indenting so that the results look nice; we do this + // in two steps to avoid str_pad mathematics. ;-) + $pad = str_pad('', $level + 1, "\t"); + $pad = str_replace("\t", ' ', $pad); + + // starting + if ($type == 'start') { + return "\n$pad"; + } + + // ending + if ($type == 'end') { + return "\n$pad"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Bold.php b/includes/pear/Text/Wiki/Render/Plain/Bold.php new file mode 100644 index 0000000..628fb51 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Bold.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Bold extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Box.php b/includes/pear/Text/Wiki/Render/Plain/Box.php new file mode 100644 index 0000000..623d073 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Box.php @@ -0,0 +1,48 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Box rule end renderer for Plain + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Box.php 193489 2005-08-15 09:50:57Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a box drawn in Plain. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Plain_Box extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Break.php b/includes/pear/Text/Wiki/Render/Plain/Break.php new file mode 100644 index 0000000..5705bed --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Break.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Plain_Break extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\n"; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Center.php b/includes/pear/Text/Wiki/Render/Plain/Center.php new file mode 100644 index 0000000..7b36367 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Center.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Center extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Code.php b/includes/pear/Text/Wiki/Render/Plain/Code.php new file mode 100644 index 0000000..5f523a3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Code.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Plain_Code extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\n" . $options['text'] . "\n\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Colortext.php b/includes/pear/Text/Wiki/Render/Plain/Colortext.php new file mode 100644 index 0000000..d577fe0 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Colortext.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Colortext extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Deflist.php b/includes/pear/Text/Wiki/Render/Plain/Deflist.php new file mode 100644 index 0000000..d05593b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Deflist.php @@ -0,0 +1,59 @@ +<?php + +class Text_Wiki_Render_Plain_Deflist extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $type = $options['type']; + $pad = " "; + + switch ($type) { + + case 'list_start': + return "\n"; + break; + + case 'list_end': + return "\n\n"; + break; + + case 'term_start': + + // done! + return $pad; + break; + + case 'term_end': + return "\n"; + break; + + case 'narr_start': + + // done! + return $pad . $pad; + break; + + case 'narr_end': + return "\n"; + break; + + default: + return ''; + + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Delimiter.php b/includes/pear/Text/Wiki/Render/Plain/Delimiter.php new file mode 100644 index 0000000..0e436aa --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Delimiter.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Delimiter extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Embed.php b/includes/pear/Text/Wiki/Render/Plain/Embed.php new file mode 100644 index 0000000..3a4304f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Embed.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Embed extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return strip_tags($options['text']); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Emphasis.php b/includes/pear/Text/Wiki/Render/Plain/Emphasis.php new file mode 100644 index 0000000..8d32995 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Emphasis.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Emphasis extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Font.php b/includes/pear/Text/Wiki/Render/Plain/Font.php new file mode 100644 index 0000000..ea0c432 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Font.php @@ -0,0 +1,44 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * BBCode: extra Font rules renderer to size the text + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Font.php 192578 2005-08-06 11:36:45Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Font rule render class (used for BBCode) + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki::Text_Wiki_Render() + */ +class Text_Wiki_Render_Plain_Font extends Text_Wiki_Render { + + /** + * Renders a token into text matching the requested format. + * process the font size option + * + * @access public + * @param array $options The "options" portion of the token (second element). + * @return string The text rendered from the token options. + */ + function token($options) + { + return; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Freelink.php b/includes/pear/Text/Wiki/Render/Plain/Freelink.php new file mode 100644 index 0000000..61de294 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Freelink.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Freelink extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Function.php b/includes/pear/Text/Wiki/Render/Plain/Function.php new file mode 100644 index 0000000..6ae5666 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Function.php @@ -0,0 +1,39 @@ +<?php + +// $Id: Function.php 170080 2004-10-08 17:46:47Z pmjones $ + +class Text_Wiki_Render_Plain_Function extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + extract($options); // access, return, name, params, throws + + $output = "$access $return $name ( "; + + foreach ($params as $key => $val) { + $output .= "{$val['type']} {$val['descr']} {$val['default']} "; + } + + $output .= ') '; + + foreach ($throws as $key => $val) { + $output .= "{$val['type']} {$val['descr']} "; + } + + return $output; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Heading.php b/includes/pear/Text/Wiki/Render/Plain/Heading.php new file mode 100644 index 0000000..306f2bf --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Heading.php @@ -0,0 +1,14 @@ +<?php + +class Text_Wiki_Render_Plain_Heading extends Text_Wiki_Render { + + function token($options) + { + if ($options['type'] == 'end') { + return "\n\n"; + } else { + return "\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Horiz.php b/includes/pear/Text/Wiki/Render/Plain/Horiz.php new file mode 100644 index 0000000..9d93764 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Horiz.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Horiz extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\n"; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Html.php b/includes/pear/Text/Wiki/Render/Plain/Html.php new file mode 100644 index 0000000..e64e94b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Html.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Plain_Html extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return strip_tags($options['text']); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Image.php b/includes/pear/Text/Wiki/Render/Plain/Image.php new file mode 100644 index 0000000..c5ddd6f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Image.php @@ -0,0 +1,22 @@ +<?php +class Text_Wiki_Render_Plain_Image extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Include.php b/includes/pear/Text/Wiki/Render/Plain/Include.php new file mode 100644 index 0000000..445990a --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Include.php @@ -0,0 +1,8 @@ +<?php +class Text_Wiki_Render_Plain_Include extends Text_Wiki_Render { + function token() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Interwiki.php b/includes/pear/Text/Wiki/Render/Plain/Interwiki.php new file mode 100644 index 0000000..762a2a0 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Interwiki.php @@ -0,0 +1,29 @@ +<?php + +class Text_Wiki_Render_Plain_Interwiki extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if (isset($options['url'])) { + // calculated by the parser (e.g. Mediawiki) + $href = $options['url']; + } else { + $href = $options['site'] . ':' . $options['page']; + } + return $options['text'] . ' (' . $href . ')'; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Italic.php b/includes/pear/Text/Wiki/Render/Plain/Italic.php new file mode 100644 index 0000000..e2a596c --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Italic.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Italic extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/List.php b/includes/pear/Text/Wiki/Render/Plain/List.php new file mode 100644 index 0000000..9983689 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/List.php @@ -0,0 +1,68 @@ +<?php + + +class Text_Wiki_Render_Plain_List extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * This rendering method is syntactically and semantically compliant + * with XHTML 1.1 in that sub-lists are part of the previous list item. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variables (type, level, count) + extract($options); + + // set up indenting so that the results look nice; we do this + // in two steps to avoid str_pad mathematics. ;-) + $pad = str_pad('', $level, "\t"); + $pad = str_replace("\t", ' ', $pad); + + switch ($type) { + + case 'bullet_list_start': + break; + + case 'bullet_list_end': + if ($level == 0) { + return "\n\n"; + } + break; + + case 'number_list_start': + break; + + case 'number_list_end': + if ($level == 0) { + return "\n\n"; + } + break; + + case 'bullet_item_start': + case 'number_item_start': + return "\n$pad"; + break; + + case 'bullet_item_end': + case 'number_item_end': + default: + // ignore item endings and all other types. + // item endings are taken care of by the other types + // depending on their place in the list. + return; + break; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Newline.php b/includes/pear/Text/Wiki/Render/Plain/Newline.php new file mode 100644 index 0000000..7c7903e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Newline.php @@ -0,0 +1,12 @@ +<?php + +class Text_Wiki_Render_Plain_Newline extends Text_Wiki_Render { + + + function token($options) + { + return "\n"; + } +} + +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Page.php b/includes/pear/Text/Wiki/Render/Plain/Page.php new file mode 100644 index 0000000..052f566 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Page.php @@ -0,0 +1,48 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Page rule end renderer for Plain + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Page.php 193514 2005-08-15 15:27:19Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders page markers in Plain. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Plain_Page extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return "\x0C"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Paragraph.php b/includes/pear/Text/Wiki/Render/Plain/Paragraph.php new file mode 100644 index 0000000..16e536f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Paragraph.php @@ -0,0 +1,31 @@ +<?php + +class Text_Wiki_Render_Plain_Paragraph extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + extract($options); //type + + if ($type == 'start') { + return ''; + } + + if ($type == 'end') { + return "\n\n"; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Phplookup.php b/includes/pear/Text/Wiki/Render/Plain/Phplookup.php new file mode 100644 index 0000000..a4268d1 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Phplookup.php @@ -0,0 +1,25 @@ +<?php + +class Text_Wiki_Render_Plain_Phplookup extends Text_Wiki_Render { + + var $conf = array('target' => '_blank'); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return trim($options['text']); + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Plugin.php b/includes/pear/Text/Wiki/Render/Plain/Plugin.php new file mode 100644 index 0000000..224ab2d --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Plugin.php @@ -0,0 +1,49 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Plugin rule end renderer for Plain + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Plugin.php 193730 2005-08-17 09:16:36Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders wiki plugins in Plain. (empty) + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Plain_Plugin extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * Plugins produce wiki markup so are processed by parsing, no tokens produced + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Prefilter.php b/includes/pear/Text/Wiki/Render/Plain/Prefilter.php new file mode 100644 index 0000000..0c63792 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Prefilter.php @@ -0,0 +1,24 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * This class implements a Text_Wiki_Render_Xhtml to "pre-filter" source text so + * that line endings are consistently \n, lines ending in a backslash \ + * are concatenated with the next line, and tabs are converted to spaces. + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Prefilter.php 248435 2007-12-17 16:19:44Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +class Text_Wiki_Render_Plain_Prefilter extends Text_Wiki_Render { + function token() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Preformatted.php b/includes/pear/Text/Wiki/Render/Plain/Preformatted.php new file mode 100644 index 0000000..db620d3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Preformatted.php @@ -0,0 +1,48 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Preformatted rule end renderer for Plain + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Preformatted.php 193477 2005-08-15 06:15:41Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders preformated text in Plain. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Plain_Preformatted extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Raw.php b/includes/pear/Text/Wiki/Render/Plain/Raw.php new file mode 100644 index 0000000..e7fa8a8 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Raw.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Raw extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Revise.php b/includes/pear/Text/Wiki/Render/Plain/Revise.php new file mode 100644 index 0000000..32bbcad --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Revise.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Plain_Revise extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Smiley.php b/includes/pear/Text/Wiki/Render/Plain/Smiley.php new file mode 100644 index 0000000..cd16e7c --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Smiley.php @@ -0,0 +1,44 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Smiley rule Plain renderer + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Smiley.php 192951 2005-08-10 11:42:08Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Smiley rule Plain render class + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki::Text_Wiki_Render() + */ +class Text_Wiki_Render_Plain_Smiley extends Text_Wiki_Render { + + /** + * Renders a token into text matching the requested format. + * process the Smileys + * + * @access public + * @param array $options The "options" portion of the token (second element). + * @return string The text rendered from the token options. + */ + function token($options) + { + return $options['symbol']; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Specialchar.php b/includes/pear/Text/Wiki/Render/Plain/Specialchar.php new file mode 100644 index 0000000..1208a8d --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Specialchar.php @@ -0,0 +1,54 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Specialchar rule end renderer for Plain + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Specialchar.php 193499 2005-08-15 11:10:38Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders special characters in Plain. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Plain_SpecialChar extends Text_Wiki_Render { + + var $types = array('~bs~' => '\\', + '~hs~' => ' ', + '~amp~' => '&', + '~ldq~' => '"', + '~rdq~' => '"', + '~lsq~' => "'", + '~rsq~' => "'", + '~c~' => '©', + '~--~' => '-', + '" -- "' => '-', + '" -- "' => '-', + '~lt~' => '<', + '~gt~' => '>'); + + function token($options) + { + if (isset($this->types[$options['char']])) { + return $this->types[$options['char']]; + } else { + return $options['char']; + } + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Strong.php b/includes/pear/Text/Wiki/Render/Plain/Strong.php new file mode 100644 index 0000000..7ff55a3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Strong.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Plain_Strong extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Subscript.php b/includes/pear/Text/Wiki/Render/Plain/Subscript.php new file mode 100644 index 0000000..222dbfc --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Subscript.php @@ -0,0 +1,48 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Subscript rule end renderer for Plain + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Subscript.php 193490 2005-08-15 10:09:06Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders subscript text in Plain. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Plain_Subscript extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Superscript.php b/includes/pear/Text/Wiki/Render/Plain/Superscript.php new file mode 100644 index 0000000..d239ee3 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Superscript.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Superscript extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Table.php b/includes/pear/Text/Wiki/Render/Plain/Table.php new file mode 100644 index 0000000..86f09aa --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Table.php @@ -0,0 +1,65 @@ +<?php + +class Text_Wiki_Render_Plain_Table extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variable names (type, attr, span) + extract($options); + + $pad = ' '; + + switch ($type) { + + case 'table_start': + return; + break; + + case 'table_end': + return; + break; + + case 'caption_start': + return; + break; + + case 'caption_end': + return "\n"; + break; + + case 'row_start': + return; + break; + + case 'row_end': + return " ||\n"; + break; + + case 'cell_start': + return " || "; + break; + + case 'cell_end': + return; + break; + + default: + return ''; + + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Tighten.php b/includes/pear/Text/Wiki/Render/Plain/Tighten.php new file mode 100644 index 0000000..e2babf4 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Tighten.php @@ -0,0 +1,10 @@ +<?php +class Text_Wiki_Render_Plain_Tighten extends Text_Wiki_Render { + + + function token() + { + return ''; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Titlebar.php b/includes/pear/Text/Wiki/Render/Plain/Titlebar.php new file mode 100644 index 0000000..2399230 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Titlebar.php @@ -0,0 +1,54 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Titlebar rule end renderer for Plain + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Titlebar.php 193500 2005-08-15 11:36:55Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a title bar in Plain. + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Plain_Titlebar extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + return '***** '; + } + + if ($options['type'] == 'end') { + return " *****\n"; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Plain/Toc.php b/includes/pear/Text/Wiki/Render/Plain/Toc.php new file mode 100644 index 0000000..34f3061 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Toc.php @@ -0,0 +1,39 @@ +<?php + +class Text_Wiki_Render_Plain_Toc extends Text_Wiki_Render { + + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // type, count, level + extract($options); + + if ($type == 'item_start') { + + // build some indenting spaces for the text + $indent = ($level - 2) * 4; + $pad = str_pad('', $indent); + return $pad; + } + + if ($type == 'item_end') { + return "\n"; + } + } + +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Tt.php b/includes/pear/Text/Wiki/Render/Plain/Tt.php new file mode 100644 index 0000000..c59ccf4 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Tt.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Plain_tt extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Underline.php b/includes/pear/Text/Wiki/Render/Plain/Underline.php new file mode 100644 index 0000000..8e4d18b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Underline.php @@ -0,0 +1,23 @@ +<?php + +class Text_Wiki_Render_Plain_Underline extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Url.php b/includes/pear/Text/Wiki/Render/Plain/Url.php new file mode 100644 index 0000000..5c758e2 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Url.php @@ -0,0 +1,29 @@ +<?php + + +class Text_Wiki_Render_Plain_Url extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start' || $options['type'] == 'end') { + return ''; + } else { + return $options['text']; + } + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Plain/Wikilink.php b/includes/pear/Text/Wiki/Render/Plain/Wikilink.php new file mode 100644 index 0000000..8337dda --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Plain/Wikilink.php @@ -0,0 +1,24 @@ +<?php + +class Text_Wiki_Render_Plain_Wikilink extends Text_Wiki_Render { + + + /** + * + * Renders a token into plain text. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?>
\ No newline at end of file diff --git a/includes/pear/Text/Wiki/Render/Xhtml.php b/includes/pear/Text/Wiki/Render/Xhtml.php new file mode 100644 index 0000000..ca611bc --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml.php @@ -0,0 +1,107 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Format class for the Xhtml rendering + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Xhtml.php 206939 2006-02-10 22:31:50Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Format class for the Xhtml rendering + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml extends Text_Wiki_Render { + + var $conf = array( + 'translate' => HTML_ENTITIES, + 'quotes' => ENT_COMPAT, + 'charset' => 'ISO-8859-1' + ); + + function pre() + { + $this->wiki->source = $this->textEncode($this->wiki->source); + } + + function post() + { + return; + } + + + /** + * Method to render text + * + * @access public + * @param string $text the text to render + * @return rendered text + * + */ + + function textEncode($text) + { + // attempt to translate HTML entities in the source. + // get the config options. + $type = $this->getConf('translate', HTML_ENTITIES); + $quotes = $this->getConf('quotes', ENT_COMPAT); + $charset = $this->getConf('charset', 'ISO-8859-1'); + + // have to check null and false because HTML_ENTITIES is a zero + if ($type === HTML_ENTITIES) { + + // keep a copy of the translated version of the delimiter + // so we can convert it back. + $new_delim = htmlentities($this->wiki->delim, $quotes, $charset); + + // convert the entities. we silence the call here so that + // errors about charsets don't pop up, per counsel from + // Jan at Horde. (http://pear.php.net/bugs/bug.php?id=4474) + $text = @htmlentities( + $text, + $quotes, + $charset + ); + + // re-convert the delimiter + $text = str_replace( + $new_delim, $this->wiki->delim, $text + ); + + } elseif ($type === HTML_SPECIALCHARS) { + + // keep a copy of the translated version of the delimiter + // so we can convert it back. + $new_delim = htmlspecialchars($this->wiki->delim, $quotes, + $charset); + + // convert the entities. we silence the call here so that + // errors about charsets don't pop up, per counsel from + // Jan at Horde. (http://pear.php.net/bugs/bug.php?id=4474) + $text = @htmlspecialchars( + $text, + $quotes, + $charset + ); + + // re-convert the delimiter + $text = str_replace( + $new_delim, $this->wiki->delim, $text + ); + } + return $text; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Address.php b/includes/pear/Text/Wiki/Render/Xhtml/Address.php new file mode 100644 index 0000000..ec98eae --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Address.php @@ -0,0 +1,54 @@ +<?php + +/** + * + * Address rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * + * @package Text_Wiki + * + * @author Michele Tomaiuolo <tomamic@yahoo.it> + * + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * + * @version CVS: $Id: Address.php 228638 2007-02-01 09:33:00Z mic $ + * + * @link http://pear.php.net/package/Text_Wiki + * + */ + +class Text_Wiki_Render_Xhtml_Address extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<address$css>"; + } + + if ($options['type'] == 'end') { + return '</address>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Anchor.php b/includes/pear/Text/Wiki/Render/Xhtml/Anchor.php new file mode 100644 index 0000000..0a42cac --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Anchor.php @@ -0,0 +1,48 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Anchor rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Anchor.php 206940 2006-02-10 23:07:03Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders an anchor target name in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Anchor extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + function token($options) + { + extract($options); // $type, $name + + if ($type == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + $format = "<a$css id=\"%s\">"; + return sprintf($format, $this->textEncode($name)); + } + + if ($type == 'end') { + return '</a>'; + } + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Blockquote.php b/includes/pear/Text/Wiki/Render/Xhtml/Blockquote.php new file mode 100644 index 0000000..edbb498 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Blockquote.php @@ -0,0 +1,72 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Blockquote rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Blockquote.php 236408 2007-05-26 18:25:45Z mic $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a blockquote in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Blockquote extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $type = $options['type']; + $level = $options['level']; + + // set up indenting so that the results look nice; we do this + // in two steps to avoid str_pad mathematics. ;-) + $pad = str_pad('', $level, "\t"); + $pad = str_replace("\t", ' ', $pad); + + // pick the css type + $css = $this->formatConf(' class="%s"', 'css'); + + if (isset($options['css'])) { + $css = ' class="' . $options['css']. '"'; + } + // starting + if ($type == 'start') { + return "$pad<blockquote$css>"; + } + + // ending + if ($type == 'end') { + return $pad . "</blockquote>\n"; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Bold.php b/includes/pear/Text/Wiki/Render/Xhtml/Bold.php new file mode 100644 index 0000000..44b088e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Bold.php @@ -0,0 +1,57 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Bold rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Bold.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders bold text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Bold extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<b$css>"; + } + + if ($options['type'] == 'end') { + return '</b>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Box.php b/includes/pear/Text/Wiki/Render/Xhtml/Box.php new file mode 100644 index 0000000..ef91883 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Box.php @@ -0,0 +1,62 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Box rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Box.php 231098 2007-03-03 23:00:54Z mic $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a box drawn in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Box extends Text_Wiki_Render { + + var $conf = array( + 'css' => 'simplebox' + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + if ($options['css']) { + $css = ' class="' . $options['css']. '"'; + } + else { + $css = $this->formatConf(' class="%s"', 'css'); + } + return "<div $css>"; + } + + if ($options['type'] == 'end') { + return '</div>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Break.php b/includes/pear/Text/Wiki/Render/Xhtml/Break.php new file mode 100644 index 0000000..6b9b040 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Break.php @@ -0,0 +1,52 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Break rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Break.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders line breaks in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Break extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $css = $this->formatConf(' class="%s"', 'css'); + return "<br$css />\n"; + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Center.php b/includes/pear/Text/Wiki/Render/Xhtml/Center.php new file mode 100644 index 0000000..b563b3f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Center.php @@ -0,0 +1,62 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Center rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Center.php 230212 2007-02-19 08:51:19Z mic $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders centered content in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Center extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->getConf('css'); + if ($css) { + return "<div class=\"$css\">"; + } + else { + return '<div style="text-align: center;">'; + } + } + + if ($options['type'] == 'end') { + return '</div>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Code.php b/includes/pear/Text/Wiki/Render/Xhtml/Code.php new file mode 100644 index 0000000..13eeef4 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Code.php @@ -0,0 +1,133 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Code rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Code.php 206940 2006-02-10 23:07:03Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders code blocks in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Code extends Text_Wiki_Render { + + var $conf = array( + 'css' => null, // class for <pre> + 'css_code' => null, // class for generic <code> + 'css_php' => null, // class for PHP <code> + 'css_html' => null, // class for HTML <code> + 'css_filename' => null // class for optional filename <div> + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = $options['text']; + $attr = $options['attr']; + $type = strtolower($attr['type']); + + $css = $this->formatConf(' class="%s"', 'css'); + $css_code = $this->formatConf(' class="%s"', 'css_code'); + $css_php = $this->formatConf(' class="%s"', 'css_php'); + $css_html = $this->formatConf(' class="%s"', 'css_html'); + $css_filename = $this->formatConf(' class="%s"', 'css_filename'); + + if ($type == 'php') { + if (substr($options['text'], 0, 5) != '<?php') { + // PHP code example: + // add the PHP tags + $text = "<?php\n" . $options['text'] . "\n?>"; // <?php + } + + // convert tabs to four spaces + $text = str_replace("\t", " ", $text); + + // colorize the code block (also converts HTML entities and adds + // <code>...</code> tags) + ob_start(); + highlight_string($text); + $text = ob_get_contents(); + ob_end_clean(); + + // replace <br /> tags with simple newlines. + // replace non-breaking space with simple spaces. + // translate HTML <font> and color to XHTML <span> and style. + // courtesy of research by A. Kalin :-). + $map = array( + '<br />' => "\n", + ' ' => ' ', + '<font' => '<span', + '</font>' => '</span>', + 'color="' => 'style="color:' + ); + $text = strtr($text, $map); + + // get rid of the last newline inside the code block + // (becuase higlight_string puts one there) + if (substr($text, -8) == "\n</code>") { + $text = substr($text, 0, -8) . "</code>"; + } + + // replace all <code> tags with classed tags + if ($css_php) { + $text = str_replace('<code>', "<code$css_php>", $text); + } + + // done + $text = "<pre$css>$text</pre>"; + + } elseif ($type == 'html' || $type == 'xhtml') { + + // HTML code example: + // add <html> opening and closing tags, + // convert tabs to four spaces, + // convert entities. + $text = str_replace("\t", " ", $text); + $text = "<html>\n$text\n</html>"; + $text = $this->textEncode($text); + $text = "<pre$css><code$css_html>$text</code></pre>"; + + } else { + // generic code example: + // convert tabs to four spaces, + // convert entities. + $text = str_replace("\t", " ", $text); + $text = $this->textEncode($text); + $text = "<pre$css><code$css_code>$text</code></pre>"; + } + + if ($css_filename && isset($attr['filename'])) { + $text = "<div$css_filename>" . + $attr['filename'] . '</div>' . $text; + } + + return "\n$text\n\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Colortext.php b/includes/pear/Text/Wiki/Render/Xhtml/Colortext.php new file mode 100644 index 0000000..d8d8c7f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Colortext.php @@ -0,0 +1,79 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Colortext rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Colortext.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders colored text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Colortext extends Text_Wiki_Render { + + var $colors = array( + 'aqua', + 'black', + 'blue', + 'fuchsia', + 'gray', + 'green', + 'lime', + 'maroon', + 'navy', + 'olive', + 'purple', + 'red', + 'silver', + 'teal', + 'white', + 'yellow' + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $type = $options['type']; + $color = $options['color']; + + if (! in_array($color, $this->colors) && $color{0} != '#') { + $color = '#' . $color; + } + + if ($type == 'start') { + return "<span style=\"color: $color;\">"; + } + + if ($options['type'] == 'end') { + return '</span>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Deflist.php b/includes/pear/Text/Wiki/Render/Xhtml/Deflist.php new file mode 100644 index 0000000..35225c0 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Deflist.php @@ -0,0 +1,87 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Deflist rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Deflist.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders definition lists in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Deflist extends Text_Wiki_Render { + + var $conf = array( + 'css_dl' => null, + 'css_dt' => null, + 'css_dd' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $type = $options['type']; + $pad = " "; + + switch ($type) { + + case 'list_start': + $css = $this->formatConf(' class="%s"', 'css_dl'); + return "<dl$css>\n"; + break; + + case 'list_end': + return "</dl>\n\n"; + break; + + case 'term_start': + $css = $this->formatConf(' class="%s"', 'css_dt'); + return $pad . "<dt$css>"; + break; + + case 'term_end': + return "</dt>\n"; + break; + + case 'narr_start': + $css = $this->formatConf(' class="%s"', 'css_dd'); + return $pad . $pad . "<dd$css>"; + break; + + case 'narr_end': + return "</dd>\n"; + break; + + default: + return ''; + + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Delimiter.php b/includes/pear/Text/Wiki/Render/Xhtml/Delimiter.php new file mode 100644 index 0000000..e39bcc7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Delimiter.php @@ -0,0 +1,46 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Delimiter rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Delimiter.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class set back the replaced delimiters in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Delimiter extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Embed.php b/includes/pear/Text/Wiki/Render/Xhtml/Embed.php new file mode 100644 index 0000000..d425411 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Embed.php @@ -0,0 +1,46 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Embed rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Embed.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class replaces the embedded php output in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Embed extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Emphasis.php b/includes/pear/Text/Wiki/Render/Xhtml/Emphasis.php new file mode 100644 index 0000000..a7910d7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Emphasis.php @@ -0,0 +1,58 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Emphasis rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Emphasis.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders emphasized text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Emphasis extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<em$css>"; + } + + if ($options['type'] == 'end') { + return '</em>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Font.php b/includes/pear/Text/Wiki/Render/Xhtml/Font.php new file mode 100644 index 0000000..f4b78ce --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Font.php @@ -0,0 +1,83 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * BBCode: extra Font rules renderer to size the text + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Font.php 192578 2005-08-06 11:36:45Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Font rule render class (used for BBCode) + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki::Text_Wiki_Render() + */ +class Text_Wiki_Render_Xhtml_Font extends Text_Wiki_Render { + +/* var $size = array( + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + 'larger', + 'smaller' + ); + var $units = array( + 'em', + 'ex', + 'px', + 'in', + 'cm', + 'mm', + 'pt', + 'pc' + ); +*/ + + /** + * Renders a token into text matching the requested format. + * process the font size option + * + * @access public + * @param array $options The "options" portion of the token (second element). + * @return string The text rendered from the token options. + */ + function token($options) + { + if ($options['type'] == 'end') { + return '</span>'; + } + if ($options['type'] != 'start') { + return ''; + } + + $ret = '<span style="'; + if (isset($options['size'])) { + $size = trim($options['size']); + if (is_numeric($size)) { + $size .= 'px'; + } + $ret .= "font-size: $size;"; + } + + return $ret.'">'; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Freelink.php b/includes/pear/Text/Wiki/Render/Xhtml/Freelink.php new file mode 100644 index 0000000..f592fdf --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Freelink.php @@ -0,0 +1,35 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Freelink rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Freelink.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * The wikilink render class. + */ +require_once 'Text/Wiki/Render/Xhtml/Wikilink.php'; + +/** + * This class renders free links in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Freelink extends Text_Wiki_Render_Xhtml_Wikilink { + // renders identically to wikilinks, only the parsing is different :-) +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Function.php b/includes/pear/Text/Wiki/Render/Xhtml/Function.php new file mode 100644 index 0000000..1ed050e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Function.php @@ -0,0 +1,108 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Function rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Function.php 206940 2006-02-10 23:07:03Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a function description in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Function extends Text_Wiki_Render { + + var $conf = array( + // list separator for params and throws + 'list_sep' => ', ', + + // the "main" format string + 'format_main' => '%access %return <b>%name</b> ( %params ) %throws', + + // the looped format string for required params + 'format_param' => '%type <i>%descr</i>', + + // the looped format string for params with default values + 'format_paramd' => '[%type <i>%descr</i> default %default]', + + // the looped format string for throws + 'format_throws' => '<b>throws</b> %type <i>%descr</i>' + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + extract($options); // name, access, return, params, throws + + // build the baseline output + $output = $this->conf['format_main']; + $output = str_replace('%access', $this->textEncode($access), $output); + $output = str_replace('%return', $this->textEncode($return), $output); + $output = str_replace('%name', $this->textEncode($name), $output); + + // build the set of params + $list = array(); + foreach ($params as $key => $val) { + + // is there a default value? + if ($val['default']) { + $tmp = $this->conf['format_paramd']; + } else { + $tmp = $this->conf['format_param']; + } + + // add the param elements + $tmp = str_replace('%type', $this->textEncode($val['type']), $tmp); + $tmp = str_replace('%descr', $this->textEncode($val['descr']), $tmp); + $tmp = str_replace('%default', $this->textEncode($val['default']), $tmp); + $list[] = $tmp; + } + + // insert params into output + $tmp = implode($this->conf['list_sep'], $list); + $output = str_replace('%params', $tmp, $output); + + // build the set of throws + $list = array(); + foreach ($throws as $key => $val) { + $tmp = $this->conf['format_throws']; + $tmp = str_replace('%type', $this->textEncode($val['type']), $tmp); + $tmp = str_replace('%descr', $this->textEncode($val['descr']), $tmp); + $list[] = $tmp; + } + + // insert throws into output + $tmp = implode($this->conf['list_sep'], $list); + $output = str_replace('%throws', $tmp, $output); + + // close the div and return the output + $output .= '</div>'; + return "\n$output\n\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Heading.php b/includes/pear/Text/Wiki/Render/Xhtml/Heading.php new file mode 100644 index 0000000..0d3c05f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Heading.php @@ -0,0 +1,88 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Heading rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Heading.php 196293 2005-09-18 13:39:39Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders headings in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Heading extends Text_Wiki_Render { + + var $conf = array( + 'css_h1' => null, + 'css_h2' => null, + 'css_h3' => null, + 'css_h4' => null, + 'css_h5' => null, + 'css_h6' => null + ); + + function token($options) + { + $collapse = null; + static $jsOutput = false; + // get nice variable names (id, type, level) + extract($options); + + switch($type) { + case 'start': + $css = $this->formatConf(' class="%s"', "css_h$level"); + return ' +<h'.$level.$css.' id="'.$id.'"'.($collapse !== null ? ' onclick="hideTOC(\''.$id.'\');"' : '').'>'; + + case 'end': + return '</h'.$level.'> +'.($collapse !== null ? '<a id="'.$id.'__link" href="javascript:void();" onclick="hideTOC(\''.$id.'\')">['.($collapse ? '+' : '-').']</a> +' : ''); + case 'startContent': + if ($collapse !== null) { + if ($jsOutput) { + $js = ''; + } else { + $js = ' +<script language="javascript"> +function hideTOC(id) { + div = document.getElementById(id+"__content"); + link = document.getElementById(id+"__link"); + if (div.style.display == "none") { + div.style.display = ""; + link.innerHTML = "[-]"; + } else { + div.style.display = "none"; + link.innerHTML = "[+]"; + } +} +</script> +'; + } + } else { + $js = ''; + } + return $js.' +<div style="'.($collapse === true ? 'display: none; ' : '').'padding: 0px; margin: 0px; border: none;" id="'.$id.'__content"> +'; + case 'endContent': + return ' +</div> +'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Horiz.php b/includes/pear/Text/Wiki/Render/Xhtml/Horiz.php new file mode 100644 index 0000000..e480f5e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Horiz.php @@ -0,0 +1,51 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Horiz rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Horiz.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders an horizontal bar in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Horiz extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $css = $this->formatConf(' class="%s"', 'css'); + return "<hr$css />\n"; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Html.php b/includes/pear/Text/Wiki/Render/Xhtml/Html.php new file mode 100644 index 0000000..7ca4218 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Html.php @@ -0,0 +1,47 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Html rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Html.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders preformated html in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Html extends Text_Wiki_Render { + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $options['text']; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Image.php b/includes/pear/Text/Wiki/Render/Xhtml/Image.php new file mode 100644 index 0000000..51def7b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Image.php @@ -0,0 +1,184 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Image rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Image.php 231923 2007-03-15 15:04:50Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class inserts an image in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Image extends Text_Wiki_Render { + + var $conf = array( + 'base' => '/', + 'url_base' => null, + 'css' => null, + 'css_link' => null + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // note the image source + $src = $options['src']; + + // is the source a local file or URL? + if (strpos($src, '://') === false) { + // the source refers to a local file. + // add the URL base to it. + $src = $this->getConf('base', '/') . $src; + } + + // stephane@metacites.net + // is the image clickable? + if (isset($options['attr']['link'])) { + // yes, the image is clickable. + // are we linked to a URL or a wiki page? + if (strpos($options['attr']['link'], '://')) { + // it's a URL, prefix the URL base + $href = $this->getConf('url_base') . $options['attr']['link']; + } else { + // it's a WikiPage; assume it exists. + /** @todo This needs to honor sprintf wikilinks (pmjones) */ + /** @todo This needs to honor interwiki (pmjones) */ + /** @todo This needs to honor freelinks (pmjones) */ + $href = $this->wiki->getRenderConf('xhtml', 'wikilink', 'view_url') . + $options['attr']['link']; + } + } else { + // image is not clickable. + $href = null; + } + // unset so it won't show up as an attribute + unset($options['attr']['link']); + + // stephane@metacites.net -- 25/07/2004 + // use CSS for all alignment + if (isset($options['attr']['align'])) { + // make sure we have a style attribute + if (!isset($options['attr']['style'])) { + // no style, set up a blank one + $options['attr']['style'] = ''; + } else { + // style exists, add a space + $options['attr']['style'] .= ' '; + } + + if ($options['attr']['align'] == 'center') { + // add a "center" style to the existing style. + $options['attr']['style'] .= + 'display: block; margin-left: auto; margin-right: auto;'; + } else { + // add a float style to the existing style + $options['attr']['style'] .= + 'float: '.$options['attr']['align']; + } + + // unset so it won't show up as an attribute + unset($options['attr']['align']); + } + + // stephane@metacites.net -- 25/07/2004 + // try to guess width and height + if (! isset($options['attr']['width']) && + ! isset($options['attr']['height'])) { + + // does the source refer to a local file or a URL? + if (strpos($src,'://')) { + // is a URL link + $imageFile = $src; + } elseif ($src[0] == '.') { + // reg at dav-muz dot net -- 2005-03-07 + // is a local file on relative path. + $imageFile = $src; # ...don't do anything because it's perfect! + } else { + // is a local file on absolute path. + $imageFile = $_SERVER['DOCUMENT_ROOT'] . $src; + } + + // attempt to get the image size + $imageSize = @getimagesize($imageFile); + + if (is_array($imageSize)) { + $options['attr']['width'] = $imageSize[0]; + $options['attr']['height'] = $imageSize[1]; + } + + } + + // start the HTML output + $output = '<img src="' . $this->textEncode($src) . '"'; + + // get the CSS class but don't add it yet + $css = $this->formatConf(' class="%s"', 'css'); + + // add the attributes to the output, and be sure to + // track whether or not we find an "alt" attribute + $alt = false; + foreach ($options['attr'] as $key => $val) { + + // track the 'alt' attribute + if (strtolower($key) == 'alt') { + $alt = true; + } + + // the 'class' attribute overrides the CSS class conf + if (strtolower($key) == 'class') { + $css = null; + } + + $key = $this->textEncode($key); + $val = $this->textEncode($val); + $output .= " $key=\"$val\""; + } + + // always add an "alt" attribute per Stephane Solliec + if (! $alt) { + $alt = $this->textEncode(basename($options['src'])); + $output .= " alt=\"$alt\""; + } + + // end the image tag with the automatic CSS class (if any) + $output .= "$css />"; + + // was the image clickable? + if ($href) { + // yes, add the href and return + $href = $this->textEncode($href); + $css = $this->formatConf(' class="%s"', 'css_link'); + $output = "<a$css href=\"$href\">$output</a>"; + } + + return $output; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Include.php b/includes/pear/Text/Wiki/Render/Xhtml/Include.php new file mode 100644 index 0000000..a152c40 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Include.php @@ -0,0 +1,32 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Include rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Include.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders included maekup in XHTML. (empty) + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Include extends Text_Wiki_Render { + function token() + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Interwiki.php b/includes/pear/Text/Wiki/Render/Xhtml/Interwiki.php new file mode 100644 index 0000000..9686b62 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Interwiki.php @@ -0,0 +1,103 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Interwiki rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Interwiki.php 231896 2007-03-15 00:08:47Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders inter wikis links in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Interwiki extends Text_Wiki_Render { + + var $conf = array( + 'sites' => array( + 'MeatBall' => 'http://www.usemod.com/cgi-bin/mb.pl?%s', + 'Advogato' => 'http://advogato.org/%s', + 'Wiki' => 'http://c2.com/cgi/wiki?%s' + ), + 'target' => '_blank', + 'css' => null + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = $options['text']; + if (isset($options['url'])) { + // calculated by the parser (e.g. Mediawiki) + $href = $options['url']; + } else { + $site = $options['site']; + // toggg 2006/02/05 page name must be url encoded (e.g. may contain spaces) + $page = $this->urlEncode($options['page']); + + if (isset($this->conf['sites'][$site])) { + $href = $this->conf['sites'][$site]; + } else { + return $text; + } + + // old form where page is at end, + // or new form with %s placeholder for sprintf()? + if (strpos($href, '%s') === false) { + // use the old form + $href = $href . $page; + } else { + // use the new form + $href = sprintf($href, $page); + } + } + + // allow for alternative targets + $target = $this->getConf('target'); + + // build base link + $css = $this->formatConf(' class="%s"', 'css'); + $text = $this->textEncode($text); + $output = "<a$css href=\"$href\""; + + // are we targeting a specific window? + if ($target && $target != '_self') { + // this is XHTML compliant, suggested by Aaron Kalin. + // code tip is actually from youngpup.net, and it + // uses the $target as the new window name. + $target = $this->textEncode($target); + $output .= " onclick=\"window.open(this.href, '$target');"; + $output .= " return false;\""; + } + + $output .= ">$text</a>"; + + return $output; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Italic.php b/includes/pear/Text/Wiki/Render/Xhtml/Italic.php new file mode 100644 index 0000000..b5cbed5 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Italic.php @@ -0,0 +1,57 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Italic rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Italic.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders italic text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Italic extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<i$css>"; + } + + if ($options['type'] == 'end') { + return '</i>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/List.php b/includes/pear/Text/Wiki/Render/Xhtml/List.php new file mode 100644 index 0000000..8ec06cb --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/List.php @@ -0,0 +1,172 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * List rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: List.php 200073 2005-11-06 10:38:25Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders bullet and ordered lists in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_List extends Text_Wiki_Render { + + var $conf = array( + 'css_ol' => null, + 'css_ol_li' => null, + 'css_ul' => null, + 'css_ul_li' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * This rendering method is syntactically and semantically compliant + * with XHTML 1.1 in that sub-lists are part of the previous list item. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variables (type, level, count) + extract($options); + + // set up indenting so that the results look nice; we do this + // in two steps to avoid str_pad mathematics. ;-) + $pad = str_pad('', $level, "\t"); + $pad = str_replace("\t", ' ', $pad); + + switch ($type) { + + case 'bullet_list_start': + + // build the base HTML + $css = $this->formatConf(' class="%s"', 'css_ul'); + $html = "<ul$css>"; + + /* + // if this is the opening block for the list, + // put an extra newline in front of it so the + // output looks nice. + if ($level == 0) { + $html = "\n$html"; + } + */ + + // done! + return $html; + break; + + case 'bullet_list_end': + + // build the base HTML + $html = "</li>\n$pad</ul>"; + + // if this is the closing block for the list, + // put extra newlines after it so the output + // looks nice. + if ($level == 0) { + $html .= "\n\n"; + } + + // done! + return $html; + break; + + case 'number_list_start': + if (isset($format)) { + $format = ' type="' . $format . '"'; + } else { + $format = ''; + } + // build the base HTML + $css = $this->formatConf(' class="%s"', 'css_ol'); + $html = "<ol{$format}{$css}>"; + + /* + // if this is the opening block for the list, + // put an extra newline in front of it so the + // output looks nice. + if ($level == 0) { + $html = "\n$html"; + } + */ + + // done! + return $html; + break; + + case 'number_list_end': + + // build the base HTML + $html = "</li>\n$pad</ol>"; + + // if this is the closing block for the list, + // put extra newlines after it so the output + // looks nice. + if ($level == 0) { + $html .= "\n\n"; + } + + // done! + return $html; + break; + + case 'bullet_item_start': + case 'number_item_start': + + // pick the proper CSS class + if ($type == 'bullet_item_start') { + $css = $this->formatConf(' class="%s"', 'css_ul_li'); + } else { + $css = $this->formatConf(' class="%s"', 'css_ol_li'); + } + + // build the base HTML + $html = "\n$pad<li$css>"; + + // for the very first item in the list, do nothing. + // but for additional items, be sure to close the + // previous item. + if ($count > 0) { + $html = "</li>$html"; + } + + // done! + return $html; + break; + + case 'bullet_item_end': + case 'number_item_end': + default: + // ignore item endings and all other types. + // item endings are taken care of by the other types + // depending on their place in the list. + return ''; + break; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Newline.php b/includes/pear/Text/Wiki/Render/Xhtml/Newline.php new file mode 100644 index 0000000..19676c6 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Newline.php @@ -0,0 +1,35 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Newline rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Newline.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders new lines in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Newline extends Text_Wiki_Render { + + + function token($options) + { + return "<br />\n"; + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Page.php b/includes/pear/Text/Wiki/Render/Xhtml/Page.php new file mode 100644 index 0000000..d9f638b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Page.php @@ -0,0 +1,46 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Page rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Page.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders page markers in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Page extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return 'PAGE MARKER HERE*&^%$#^$%*PAGEMARKERHERE'; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Paragraph.php b/includes/pear/Text/Wiki/Render/Xhtml/Paragraph.php new file mode 100644 index 0000000..fc73627 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Paragraph.php @@ -0,0 +1,59 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Paragraph rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Paragraph.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders paragraphs in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Paragraph extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + extract($options); //type + + if ($type == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<p$css>"; + } + + if ($type == 'end') { + return "</p>\n\n"; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Phplookup.php b/includes/pear/Text/Wiki/Render/Xhtml/Phplookup.php new file mode 100644 index 0000000..e77b0b7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Phplookup.php @@ -0,0 +1,81 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Phplookup rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Phplookup.php 231896 2007-03-15 00:08:47Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a link to php functions description in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Phplookup extends Text_Wiki_Render { + + var $conf = array( + 'target' => '_blank', + 'css' => null + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = trim($options['text']); + $css = $this->formatConf(' class="%s"', 'css'); + + // start the html + $output = "<a$css"; + + // are we targeting another window? + $target = $this->getConf('target', ''); + if ($target && $target != '_self') { + // use a "popup" window. this is XHTML compliant, suggested by + // Aaron Kalin. uses the $target as the new window name. + $target = $this->textEncode($target); + $output .= " onclick=\"window.open(this.href, '$target');"; + $output .= " return false;\""; + } + + // take off the final parens for functions + if (substr($text, -2) == '()') { + $q = substr($text, 0, -2); + } else { + $q = $text; + } + + // toggg 2006/02/05 page name must be url encoded (e.g. may contain spaces) + $q = $this->urlEncode($q); + $text = $this->textEncode($text); + + // finish and return + $output .= " href=\"http://php.net/$q\">$text</a>"; + return $output; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Plugin.php b/includes/pear/Text/Wiki/Render/Xhtml/Plugin.php new file mode 100644 index 0000000..048ba80 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Plugin.php @@ -0,0 +1,47 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Plugin rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Plugin.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders wiki plugins in XHTML. (empty) + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Plugin extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * Plugins produce wiki markup so are processed by parsing, no tokens produced + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Prefilter.php b/includes/pear/Text/Wiki/Render/Xhtml/Prefilter.php new file mode 100644 index 0000000..b0ae26e --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Prefilter.php @@ -0,0 +1,34 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Prefilter rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Prefilter.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class implements a Text_Wiki_Render_Xhtml to "pre-filter" source text so + * that line endings are consistently \n, lines ending in a backslash \ + * are concatenated with the next line, and tabs are converted to spaces. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Prefilter extends Text_Wiki_Render { + function token() + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Preformatted.php b/includes/pear/Text/Wiki/Render/Xhtml/Preformatted.php new file mode 100644 index 0000000..a68ce76 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Preformatted.php @@ -0,0 +1,47 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Preformatted rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Preformatted.php 229275 2007-02-07 13:40:44Z mic $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders preformated text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Preformatted extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + $text = $this->textEncode($options['text']); + return '<pre>'.$text.'</pre>'; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Raw.php b/includes/pear/Text/Wiki/Render/Xhtml/Raw.php new file mode 100644 index 0000000..3027153 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Raw.php @@ -0,0 +1,46 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Raw rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Raw.php 214538 2006-06-09 21:32:24Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders not processed blocks in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Raw extends Text_Wiki_Render { + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + return $this->textEncode($options['text']); + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Revise.php b/includes/pear/Text/Wiki/Render/Xhtml/Revise.php new file mode 100644 index 0000000..f81fd7f --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Revise.php @@ -0,0 +1,68 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Revise rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Revise.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders revision marks in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Revise extends Text_Wiki_Render { + + var $conf = array( + 'css_ins' => null, + 'css_del' => null + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'del_start') { + $css = $this->formatConf(' class="%s"', 'css_del'); + return "<del$css>"; + } + + if ($options['type'] == 'del_end') { + return "</del>"; + } + + if ($options['type'] == 'ins_start') { + $css = $this->formatConf(' class="%s"', 'css_ins'); + return "<ins$css>"; + } + + if ($options['type'] == 'ins_end') { + return "</ins>"; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Smiley.php b/includes/pear/Text/Wiki/Render/Xhtml/Smiley.php new file mode 100644 index 0000000..778796b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Smiley.php @@ -0,0 +1,74 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Smiley rule Xhtml renderer + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Smiley.php 206940 2006-02-10 23:07:03Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * Smiley rule Xhtml render class + * + * @category Text + * @package Text_Wiki + * @author Bertrand Gugger <bertrand@toggg.com> + * @copyright 2005 bertrand Gugger + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + * @see Text_Wiki::Text_Wiki_Render() + */ +class Text_Wiki_Render_Xhtml_Smiley extends Text_Wiki_Render { + + /** + * Configuration keys for this rule + * 'prefix' => the path to smileys images inclusive file name prefix, + * starts with '/' ==> abolute reference + * if no file names prefix but some folder, terminates with '/' + * 'extension' => the file extension (inclusive '.'), e.g. : + * if prefix 'smileys/icon_' and extension '.gif' + * ':)' whose name is 'smile' will give relative file 'smileys/icon_smile.gif' + * if prefix '/image/smileys/' and extension '.png': absolute '/image/smileys/smile.gif' + * 'css' => optional style applied to smileys + * + * @access public + * @var array 'config-key' => mixed config-value + */ + var $conf = array( + 'prefix' => 'images/smiles/icon_', + 'extension' => '.gif', + 'css' => null + ); + + /** + * Renders a token into text matching the requested format. + * process the Smileys + * + * @access public + * @param array $options The "options" portion of the token (second element). + * @return string The text rendered from the token options. + */ + function token($options) + { + $imageFile = $this->getConf('prefix') . $options['name'] . $this->getConf('extension'); + + // attempt to get the image size + $imageSize = @getimagesize($imageFile); + + // return the HTML output + return '<img src="' . $this->textEncode($imageFile) . '"' . + (is_array($imageSize) ? + ' width="' . $imageSize[0] . '" height="' . $imageSize[1] .'"' : '') . + ' alt="' . $options['desc'] . '"' . + $this->formatConf(' class="%s"', 'css') . ' />'; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Specialchar.php b/includes/pear/Text/Wiki/Render/Xhtml/Specialchar.php new file mode 100644 index 0000000..4372c02 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Specialchar.php @@ -0,0 +1,52 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Specialchar rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Specialchar.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders special characters in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_SpecialChar extends Text_Wiki_Render { + + var $types = array('~bs~' => '\', + '~hs~' => ' ', + '~amp~' => '&', + '~ldq~' => '“', + '~rdq~' => '”', + '~lsq~' => '‘', + '~rsq~' => '’', + '~c~' => '©', + '~--~' => '—', + '" -- "' => '—', + '" -- "' => '—', + '~lt~' => '<', + '~gt~' => '>'); + + function token($options) + { + if (isset($this->types[$options['char']])) { + return $this->types[$options['char']]; + } else { + return '&#'.substr($options['char'], 1, -1).';'; + } + } +} + +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Strong.php b/includes/pear/Text/Wiki/Render/Xhtml/Strong.php new file mode 100644 index 0000000..da816a7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Strong.php @@ -0,0 +1,58 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Strong rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Strong.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders text marked as strong in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Strong extends Text_Wiki_Render { + + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<strong$css>"; + } + + if ($options['type'] == 'end') { + return '</strong>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Subscript.php b/includes/pear/Text/Wiki/Render/Xhtml/Subscript.php new file mode 100644 index 0000000..653a110 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Subscript.php @@ -0,0 +1,57 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Subscript rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Subscript.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders subscript text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Subscript extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<sub$css>"; + } + + if ($options['type'] == 'end') { + return '</sub>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Superscript.php b/includes/pear/Text/Wiki/Render/Xhtml/Superscript.php new file mode 100644 index 0000000..1a39453 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Superscript.php @@ -0,0 +1,57 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Superscript rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Superscript.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders superscript text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Superscript extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<sup$css>"; + } + + if ($options['type'] == 'end') { + return '</sup>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Table.php b/includes/pear/Text/Wiki/Render/Xhtml/Table.php new file mode 100644 index 0000000..670ff7b --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Table.php @@ -0,0 +1,140 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Table rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Table.php 202250 2005-12-06 15:29:29Z ritzmo $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders tables in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Table extends Text_Wiki_Render { + + var $conf = array( + 'css_table' => null, + 'css_caption' => null, + 'css_tr' => null, + 'css_th' => null, + 'css_td' => null + ); + + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variable names (type, attr, span) + $span = $rowspan = 1; + extract($options); + + // free format + $format = isset($format) ? ' '. $format : ''; + + $pad = ' '; + + switch ($type) { + + case 'table_start': + $css = $this->formatConf(' class="%s"', 'css_table'); + return "\n\n<table$css$format>\n"; + break; + + case 'table_end': + return "</table>\n\n"; + break; + + case 'caption_start': + $css = $this->formatConf(' class="%s"', 'css_caption'); + return "<caption$css$format>\n"; + break; + + case 'caption_end': + return "</caption>\n"; + break; + + case 'row_start': + $css = $this->formatConf(' class="%s"', 'css_tr'); + return "$pad<tr$css$format>\n"; + break; + + case 'row_end': + return "$pad</tr>\n"; + break; + + case 'cell_start': + + // base html + $html = $pad . $pad; + + // is this a TH or TD cell? + if ($attr == 'header') { + // start a header cell + $css = $this->formatConf(' class="%s"', 'css_th'); + $html .= "<th$css"; + } else { + // start a normal cell + $css = $this->formatConf(' class="%s"', 'css_td'); + $html .= "<td$css"; + } + + // add the column span + if ($span > 1) { + $html .= " colspan=\"$span\""; + } + + // add the row span + if ($rowspan > 1) { + $html .= " rowspan=\"$rowspan\""; + } + + // add alignment + if ($attr != 'header' && $attr != '') { + $html .= " style=\"text-align: $attr;\""; + } + + // done! + $html .= "$format>"; + return $html; + break; + + case 'cell_end': + if ($attr == 'header') { + return "</th>\n"; + } else { + return "</td>\n"; + } + break; + + default: + return ''; + + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Tighten.php b/includes/pear/Text/Wiki/Render/Xhtml/Tighten.php new file mode 100644 index 0000000..30d6dfd --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Tighten.php @@ -0,0 +1,34 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Tighten rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Tighten.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class makes the tightening in XHTML. (empty) + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Tighten extends Text_Wiki_Render { + + + function token() + { + return ''; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Titlebar.php b/includes/pear/Text/Wiki/Render/Xhtml/Titlebar.php new file mode 100644 index 0000000..d6e24a8 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Titlebar.php @@ -0,0 +1,57 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Titlebar rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Titlebar.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders a title bar in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Titlebar extends Text_Wiki_Render { + + var $conf = array( + 'css' => 'titlebar' + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<div$css>"; + } + + if ($options['type'] == 'end') { + return '</div>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Toc.php b/includes/pear/Text/Wiki/Render/Xhtml/Toc.php new file mode 100644 index 0000000..2b68b68 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Toc.php @@ -0,0 +1,115 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Toc rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Toc.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class inserts a table of content in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Toc extends Text_Wiki_Render { + + var $conf = array( + 'css_list' => null, + 'css_item' => null, + 'title' => '<strong>Table of Contents</strong>', + 'div_id' => 'toc', + 'collapse' => true + ); + + var $min = 2; + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // type, id, level, count, attr + extract($options); + + switch ($type) { + + case 'list_start': + + $css = $this->getConf('css_list'); + $html = ''; + + // collapse div within a table? + if ($this->getConf('collapse')) { + $html .= '<table border="0" cellspacing="0" cellpadding="0">'; + $html .= "<tr><td>\n"; + } + + // add the div, class, and id + $html .= '<div'; + if ($css) { + $html .= " class=\"$css\""; + } + + $div_id = $this->getConf('div_id'); + if ($div_id) { + $html .= " id=\"$div_id\""; + } + + // add the title, and done + $html .= '>'; + $html .= $this->getConf('title'); + return $html; + break; + + case 'list_end': + if ($this->getConf('collapse')) { + return "\n</div>\n</td></tr></table>\n\n"; + } else { + return "\n</div>\n\n"; + } + break; + + case 'item_start': + $html = "\n\t<div"; + + $css = $this->getConf('css_item'); + if ($css) { + $html .= " class=\"$css\""; + } + + $pad = ($level - $this->min); + $html .= " style=\"margin-left: {$pad}em;\">"; + + $html .= "<a href=\"#$id\">"; + return $html; + break; + + case 'item_end': + return "</a></div>"; + break; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Tt.php b/includes/pear/Text/Wiki/Render/Xhtml/Tt.php new file mode 100644 index 0000000..3cefe85 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Tt.php @@ -0,0 +1,58 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Tt rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Tt.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders monospaced text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Tt extends Text_Wiki_Render { + + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<tt$css>"; + } + + if ($options['type'] == 'end') { + return '</tt>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Underline.php b/includes/pear/Text/Wiki/Render/Xhtml/Underline.php new file mode 100644 index 0000000..a6cbf59 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Underline.php @@ -0,0 +1,57 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Underline rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Underline.php 191862 2005-07-30 08:03:29Z toggg $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders underlined text in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Underline extends Text_Wiki_Render { + + var $conf = array( + 'css' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + if ($options['type'] == 'start') { + $css = $this->formatConf(' class="%s"', 'css'); + return "<u$css>"; + } + + if ($options['type'] == 'end') { + return '</u>'; + } + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Url.php b/includes/pear/Text/Wiki/Render/Xhtml/Url.php new file mode 100644 index 0000000..9139be7 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Url.php @@ -0,0 +1,131 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Url rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Url.php 236400 2007-05-26 17:15:41Z mic $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders URL links in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Url extends Text_Wiki_Render { + + + var $conf = array( + 'target' => '_blank', + 'images' => true, + 'img_ext' => array('jpg', 'jpeg', 'gif', 'png'), + 'css_inline' => null, + 'css_footnote' => null, + 'css_descr' => null, + 'css_img' => null + ); + + /** + * + * Renders a token into text matching the requested format. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // create local variables from the options array (text, + // href, type) + extract($options); + + // find the rightmost dot and determine the filename + // extension. + $pos = strrpos($href, '.'); + $ext = strtolower(substr($href, $pos + 1)); + $href = $this->textEncode($href); + + // does the filename extension indicate an image file? + if ($this->getConf('images') && + in_array($ext, $this->getConf('img_ext', array()))) { + + // create alt text for the image + if (! isset($text) || $text == '') { + $text = basename($href); + $text = $this->textEncode($text); + } + + // generate an image tag + $css = $this->formatConf(' class="%s"', 'css_img'); + $start = "<img$css src=\"$href\" alt=\"$text\" title=\"$text\" /><!-- "; + $end = " -->"; + + } else { + + // should we build a target clause? + if ($href{0} == '#' || + strtolower(substr($href, 0, 7)) == 'mailto:') { + // targets not allowed for on-page anchors + // and mailto: links. + $target = ''; + } else { + // allow targets on non-anchor non-mailto links + $target = $this->getConf('target'); + } + + // generate a regular link (not an image) + $text = $this->textEncode($text); + $css = $this->formatConf(' class="%s"', "css_$type"); + $start = "<a$css href=\"$href\""; + + if ($target && $target != '_self') { + // use a "popup" window. this is XHTML compliant, suggested by + // Aaron Kalin. uses the $target as the new window name. + $target = $this->textEncode($target); + $start .= " onclick=\"window.open(this.href, '$target');"; + $start .= " return false;\""; + } + + if (isset($name)) { + $start .= " id=\"$name\""; + } + + // finish up output + $start .= ">"; + $end = "</a>"; + + // make numbered references look like footnotes when no + // CSS class specified, make them superscript by default + if ($type == 'footnote' && ! $css) { + $start = '<sup>' . $start; + $end = $end . '</sup>'; + } + } + + if ($options['type'] == 'start') { + $output = $start; + } else if ($options['type'] == 'end') { + $output = $end; + } else { + $output = $start . $text . $end; + } + return $output; + } +} +?> diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Wikilink.php b/includes/pear/Text/Wiki/Render/Xhtml/Wikilink.php new file mode 100644 index 0000000..b93b000 --- /dev/null +++ b/includes/pear/Text/Wiki/Render/Xhtml/Wikilink.php @@ -0,0 +1,177 @@ +<?php +// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: +/** + * Wikilink rule end renderer for Xhtml + * + * PHP versions 4 and 5 + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id: Wikilink.php 224670 2006-12-08 21:25:24Z justinpatrin $ + * @link http://pear.php.net/package/Text_Wiki + */ + +/** + * This class renders wiki links in XHTML. + * + * @category Text + * @package Text_Wiki + * @author Paul M. Jones <pmjones@php.net> + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: @package_version@ + * @link http://pear.php.net/package/Text_Wiki + */ +class Text_Wiki_Render_Xhtml_Wikilink extends Text_Wiki_Render { + + var $conf = array( + 'pages' => array(), // set to null or false to turn off page checks + 'view_url' => 'http://example.com/index.php?page=%s', + 'new_url' => 'http://example.com/new.php?page=%s', + 'new_text' => '?', + 'new_text_pos' => 'after', // 'before', 'after', or null/false + 'css' => null, + 'css_new' => null, + 'exists_callback' => null // call_user_func() callback + ); + + + /** + * + * Renders a token into XHTML. + * + * @access public + * + * @param array $options The "options" portion of the token (second + * element). + * + * @return string The text rendered from the token options. + * + */ + + function token($options) + { + // make nice variable names (page, anchor, text) + extract($options); + + // is there a "page existence" callback? + // we need to access it directly instead of through + // getConf() because we'll need a reference (for + // object instance method callbacks). + if (isset($this->conf['exists_callback'])) { + $callback =& $this->conf['exists_callback']; + } else { + $callback = false; + } + + if ($callback) { + // use the callback function + $exists = call_user_func($callback, $page); + } else { + // no callback, go to the naive page array. + $list = $this->getConf('pages'); + if (is_array($list)) { + // yes, check against the page list + $exists = in_array($page, $list); + } else { + // no, assume it exists + $exists = true; + } + } + + $anchor = '#'.$this->urlEncode(substr($anchor, 1)); + + // does the page exist? + if ($exists) { + + // PAGE EXISTS. + + // link to the page view, but we have to build + // the HREF. we support both the old form where + // the page always comes at the end, and the new + // form that uses %s for sprintf() + $href = $this->getConf('view_url'); + + if (strpos($href, '%s') === false) { + // use the old form (page-at-end) + $href = $href . $this->urlEncode($page) . $anchor; + } else { + // use the new form (sprintf format string) + $href = sprintf($href, $this->urlEncode($page)) . $anchor; + } + + // get the CSS class and generate output + $css = ' class="'.$this->textEncode($this->getConf('css')).'"'; + + $start = '<a'.$css.' href="'.$this->textEncode($href).'">'; + $end = '</a>'; + } else { + + // PAGE DOES NOT EXIST. + + // link to a create-page url, but only if new_url is set + $href = $this->getConf('new_url', null); + + // set the proper HREF + if (! $href || trim($href) == '') { + + // no useful href, return the text as it is + //TODO: This is no longer used, need to look closer into this branch + $output = $text; + + } else { + + // yes, link to the new-page href, but we have to build + // it. we support both the old form where + // the page always comes at the end, and the new + // form that uses sprintf() + if (strpos($href, '%s') === false) { + // use the old form + $href = $href . $this->urlEncode($page); + } else { + // use the new form + $href = sprintf($href, $this->urlEncode($page)); + } + } + + // get the appropriate CSS class and new-link text + $css = ' class="'.$this->textEncode($this->getConf('css_new')).'"'; + $new = $this->getConf('new_text'); + + // what kind of linking are we doing? + $pos = $this->getConf('new_text_pos'); + if (! $pos || ! $new) { + // no position (or no new_text), use css only on the page name + + $start = '<a'.$css.' href="'.$this->textEncode($href).'">'; + $end = '</a>'; + } elseif ($pos == 'before') { + // use the new_text BEFORE the page name + $start = '<a'.$css.' href="'.$this->textEncode($href).'">'.$this->textEncode($new).'</a>'; + $end = ''; + } else { + // default, use the new_text link AFTER the page name + $start = ''; + $end = '<a'.$css.' href="'.$this->textEncode($href).'">'.$this->textEncode($new).'</a>'; + } + } + if (!strlen($text)) { + $start .= $this->textEncode($page); + } + if (isset($type)) { + switch ($type) { + case 'start': + $output = $start; + break; + case 'end': + $output = $end; + break; + } + } else { + $output = $start.$this->textEncode($text).$end; + } + return $output; + } +} +?> diff --git a/includes/pear/XML/Util.php b/includes/pear/XML/Util.php new file mode 100644 index 0000000..f5927b1 --- /dev/null +++ b/includes/pear/XML/Util.php @@ -0,0 +1,911 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * XML_Util + * + * XML Utilities package + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2003-2008 Stephan Schmidt <schst@php.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Util + * @author Stephan Schmidt <schst@php.net> + * @copyright 2003-2008 Stephan Schmidt <schst@php.net> + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Util.php,v 1.38 2008/11/13 00:03:38 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Util + */ + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_CHARS', 51); + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_START', 52); + +/** + * error code for non-scalar tag content + */ +define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60); + +/** + * error code for missing tag name + */ +define('XML_UTIL_ERROR_NO_TAG_NAME', 61); + +/** + * replace XML entities + */ +define('XML_UTIL_REPLACE_ENTITIES', 1); + +/** + * embedd content in a CData Section + */ +define('XML_UTIL_CDATA_SECTION', 5); + +/** + * do not replace entitites + */ +define('XML_UTIL_ENTITIES_NONE', 0); + +/** + * replace all XML entitites + * This setting will replace <, >, ", ' and & + */ +define('XML_UTIL_ENTITIES_XML', 1); + +/** + * replace only required XML entitites + * This setting will replace <, " and & + */ +define('XML_UTIL_ENTITIES_XML_REQUIRED', 2); + +/** + * replace HTML entitites + * @link http://www.php.net/htmlentities + */ +define('XML_UTIL_ENTITIES_HTML', 3); + +/** + * Collapse all empty tags. + */ +define('XML_UTIL_COLLAPSE_ALL', 1); + +/** + * Collapse only empty XHTML tags that have no end tag. + */ +define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2); + +/** + * utility class for working with XML documents + * + + * @category XML + * @package XML_Util + * @author Stephan Schmidt <schst@php.net> + * @copyright 2003-2008 Stephan Schmidt <schst@php.net> + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.2.1 + * @link http://pear.php.net/package/XML_Util + */ +class XML_Util +{ + /** + * return API version + * + * @return string $version API version + * @access public + * @static + */ + function apiVersion() + { + return '1.1'; + } + + /** + * replace XML entities + * + * With the optional second parameter, you may select, which + * entities should be replaced. + * + * <code> + * require_once 'XML/Util.php'; + * + * // replace XML entites: + * $string = XML_Util::replaceEntities('This string contains < & >.'); + * </code> + * + * With the optional third parameter, you may pass the character encoding + * <code> + * require_once 'XML/Util.php'; + * + * // replace XML entites in UTF-8: + * $string = XML_Util::replaceEntities( + * 'This string contains < & > as well as ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * </code> + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the htmlentities() function + * + * @return string string with replaced chars + * @access public + * @static + * @see reverseEntities() + */ + function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return htmlentities($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * reverse XML entities + * + * With the optional second parameter, you may select, which + * entities should be reversed. + * + * <code> + * require_once 'XML/Util.php'; + * + * // reverse XML entites: + * $string = XML_Util::reverseEntities('This string contains < & >.'); + * </code> + * + * With the optional third parameter, you may pass the character encoding + * <code> + * require_once 'XML/Util.php'; + * + * // reverse XML entites in UTF-8: + * $string = XML_Util::reverseEntities( + * 'This string contains < & > as well as' + * . ' ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * </code> + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the html_entity_decode() function + * + * @return string string with replaced chars + * @access public + * @static + * @see replaceEntities() + */ + function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + ''' => '\'' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return html_entity_decode($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * build an xml declaration + * + * <code> + * require_once 'XML/Util.php'; + * + * // get an XML declaration: + * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true); + * </code> + * + * @param string $version xml version + * @param string $encoding character encoding + * @param bool $standalone document is standalone (or not) + * + * @return string xml declaration + * @access public + * @static + * @uses attributesToString() to serialize the attributes of the XML declaration + */ + function getXMLDeclaration($version = '1.0', $encoding = null, + $standalone = null) + { + $attributes = array( + 'version' => $version, + ); + // add encoding + if ($encoding !== null) { + $attributes['encoding'] = $encoding; + } + // add standalone, if specified + if ($standalone !== null) { + $attributes['standalone'] = $standalone ? 'yes' : 'no'; + } + + return sprintf('<?xml%s?>', + XML_Util::attributesToString($attributes, false)); + } + + /** + * build a document type declaration + * + * <code> + * require_once 'XML/Util.php'; + * + * // get a doctype declaration: + * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd'); + * </code> + * + * @param string $root name of the root tag + * @param string $uri uri of the doctype definition + * (or array with uri and public id) + * @param string $internalDtd internal dtd entries + * + * @return string doctype declaration + * @access public + * @static + * @since 0.2 + */ + function getDocTypeDeclaration($root, $uri = null, $internalDtd = null) + { + if (is_array($uri)) { + $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']); + } elseif (!empty($uri)) { + $ref = sprintf(' SYSTEM "%s"', $uri); + } else { + $ref = ''; + } + + if (empty($internalDtd)) { + return sprintf('<!DOCTYPE %s%s>', $root, $ref); + } else { + return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd); + } + } + + /** + * create string representation of an attribute list + * + * <code> + * require_once 'XML/Util.php'; + * + * // build an attribute string + * $att = array( + * 'foo' => 'bar', + * 'argh' => 'tomato' + * ); + * + * $attList = XML_Util::attributesToString($att); + * </code> + * + * @param array $attributes attribute array + * @param bool|array $sort sort attribute list alphabetically, + * may also be an assoc array containing + * the keys 'sort', 'multiline', 'indent', + * 'linebreak' and 'entities' + * @param bool $multiline use linebreaks, if more than + * one attribute is given + * @param string $indent string used for indentation of + * multiline attributes + * @param string $linebreak string used for linebreaks of + * multiline attributes + * @param int $entities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_NONE, + * XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * + * @return string string representation of the attributes + * @access public + * @static + * @uses replaceEntities() to replace XML entities in attribute values + * @todo allow sort also to be an options array + */ + function attributesToString($attributes, $sort = true, $multiline = false, + $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML) + { + /* + * second parameter may be an array + */ + if (is_array($sort)) { + if (isset($sort['multiline'])) { + $multiline = $sort['multiline']; + } + if (isset($sort['indent'])) { + $indent = $sort['indent']; + } + if (isset($sort['linebreak'])) { + $multiline = $sort['linebreak']; + } + if (isset($sort['entities'])) { + $entities = $sort['entities']; + } + if (isset($sort['sort'])) { + $sort = $sort['sort']; + } else { + $sort = true; + } + } + $string = ''; + if (is_array($attributes) && !empty($attributes)) { + if ($sort) { + ksort($attributes); + } + if ( !$multiline || count($attributes) == 1) { + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + if ($entities === XML_UTIL_CDATA_SECTION) { + $entities = XML_UTIL_ENTITIES_XML; + } + $value = XML_Util::replaceEntities($value, $entities); + } + $string .= ' ' . $key . '="' . $value . '"'; + } + } else { + $first = true; + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + $value = XML_Util::replaceEntities($value, $entities); + } + if ($first) { + $string .= ' ' . $key . '="' . $value . '"'; + $first = false; + } else { + $string .= $linebreak . $indent . $key . '="' . $value . '"'; + } + } + } + } + return $string; + } + + /** + * Collapses empty tags. + * + * @param string $xml XML + * @param int $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL) + * or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones. + * + * @return string XML + * @access public + * @static + * @todo PEAR CS - unable to avoid "space after open parens" error + * in the IF branch + */ + function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) + { + if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) { + return preg_replace( + '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|' + . 'param)([^>]*)><\/\\1>/s', + '<\\1\\2 />', + $xml); + } else { + return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml); + } + } + + /** + * create a tag + * + * This method will call XML_Util::createTagFromArray(), which + * is more flexible. + * + * <code> + * require_once 'XML/Util.php'; + * + * // create an XML tag: + * $tag = XML_Util::createTag('myNs:myTag', + * array('foo' => 'bar'), + * 'This is inside the tag', + * 'http://www.w3c.org/myNs#'); + * </code> + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param mixed $content the content + * @param string $namespaceUri URI of the namespace + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where + * each attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTagFromArray() + * @uses createTagFromArray() to create the tag + */ + function createTag($qname, $attributes = array(), $content = null, + $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + $tag = array( + 'qname' => $qname, + 'attributes' => $attributes + ); + + // add tag content + if ($content !== null) { + $tag['content'] = $content; + } + + // add namespace Uri + if ($namespaceUri !== null) { + $tag['namespaceUri'] = $namespaceUri; + } + + return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, + $indent, $linebreak, $sortAttributes); + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * <pre> + * array( + * // qualified name of the tag + * 'qname' => $qname + * + * // namespace prefix (optional, if qname is specified or no namespace) + * 'namespace' => $namespace + * + * // local part of the tagname (optional, if qname is specified) + * 'localpart' => $localpart, + * + * // array containing all attributes (optional) + * 'attributes' => array(), + * + * // tag content (optional) + * 'content' => $content, + * + * // namespaceUri for the given namespace (optional) + * 'namespaceUri' => $namespaceUri + * ) + * </pre> + * + * <code> + * require_once 'XML/Util.php'; + * + * $tag = array( + * 'qname' => 'foo:bar', + * 'namespaceUri' => 'http://foo.com', + * 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + * 'content' => 'I\'m inside the tag', + * ); + * // creating a tag with qualified name and namespaceUri + * $string = XML_Util::createTagFromArray($tag); + * </code> + * + * @param array $tag tag definition + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTag() + * @uses attributesToString() to serialize the attributes of the tag + * @uses splitQualifiedName() to get local part and namespace of a qualified name + * @uses createCDataSection() + * @uses raiseError() + */ + function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + if (isset($tag['content']) && !is_scalar($tag['content'])) { + return XML_Util::raiseError('Supplied non-scalar value as tag content', + XML_UTIL_ERROR_NON_SCALAR_CONTENT); + } + + if (!isset($tag['qname']) && !isset($tag['localPart'])) { + return XML_Util::raiseError('You must either supply a qualified name ' + . '(qname) or local tag name (localPart).', + XML_UTIL_ERROR_NO_TAG_NAME); + } + + // if no attributes hav been set, use empty attributes + if (!isset($tag['attributes']) || !is_array($tag['attributes'])) { + $tag['attributes'] = array(); + } + + if (isset($tag['namespaces'])) { + foreach ($tag['namespaces'] as $ns => $uri) { + $tag['attributes']['xmlns:' . $ns] = $uri; + } + } + + if (!isset($tag['qname'])) { + // qualified name is not given + + // check for namespace + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart']; + } else { + $tag['qname'] = $tag['localPart']; + } + } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) { + // namespace URI is set, but no namespace + + $parts = XML_Util::splitQualifiedName($tag['qname']); + + $tag['localPart'] = $parts['localPart']; + if (isset($parts['namespace'])) { + $tag['namespace'] = $parts['namespace']; + } + } + + if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) { + // is a namespace given + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['attributes']['xmlns:' . $tag['namespace']] = + $tag['namespaceUri']; + } else { + // define this Uri as the default namespace + $tag['attributes']['xmlns'] = $tag['namespaceUri']; + } + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($tag['qname'])+2)); + } + } + + // create attribute list + $attList = XML_Util::attributesToString($tag['attributes'], + $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities); + if (!isset($tag['content']) || (string)$tag['content'] == '') { + $tag = sprintf('<%s%s />', $tag['qname'], $attList); + } else { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_NONE: + break; + case XML_UTIL_CDATA_SECTION: + $tag['content'] = XML_Util::createCDataSection($tag['content']); + break; + default: + $tag['content'] = XML_Util::replaceEntities($tag['content'], + $replaceEntities); + break; + } + $tag = sprintf('<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'], + $tag['qname']); + } + return $tag; + } + + /** + * create a start element + * + * <code> + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createStartElement('myNs:myTag', + * array('foo' => 'bar') ,'http://www.w3c.org/myNs#'); + * </code> + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param string $namespaceUri URI of the namespace + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes (_auto indents + * attributes so they start at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML start element + * @access public + * @static + * @see createEndElement(), createTag() + */ + function createStartElement($qname, $attributes = array(), $namespaceUri = null, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + // if no attributes hav been set, use empty attributes + if (!isset($attributes) || !is_array($attributes)) { + $attributes = array(); + } + + if ($namespaceUri != null) { + $parts = XML_Util::splitQualifiedName($qname); + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($qname)+2)); + } + } + + if ($namespaceUri != null) { + // is a namespace given + if (isset($parts['namespace']) && !empty($parts['namespace'])) { + $attributes['xmlns:' . $parts['namespace']] = $namespaceUri; + } else { + // define this Uri as the default namespace + $attributes['xmlns'] = $namespaceUri; + } + } + + // create attribute list + $attList = XML_Util::attributesToString($attributes, $sortAttributes, + $multiline, $indent, $linebreak); + $element = sprintf('<%s%s>', $qname, $attList); + return $element; + } + + /** + * create an end element + * + * <code> + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createEndElement('myNs:myTag'); + * </code> + * + * @param string $qname qualified tagname (including namespace) + * + * @return string XML end element + * @access public + * @static + * @see createStartElement(), createTag() + */ + function createEndElement($qname) + { + $element = sprintf('</%s>', $qname); + return $element; + } + + /** + * create an XML comment + * + * <code> + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createComment('I am a comment'); + * </code> + * + * @param string $content content of the comment + * + * @return string XML comment + * @access public + * @static + */ + function createComment($content) + { + $comment = sprintf('<!-- %s -->', $content); + return $comment; + } + + /** + * create a CData section + * + * <code> + * require_once 'XML/Util.php'; + * + * // create a CData section + * $tag = XML_Util::createCDataSection('I am content.'); + * </code> + * + * @param string $data data of the CData section + * + * @return string CData section with content + * @access public + * @static + */ + function createCDataSection($data) + { + return sprintf('<![CDATA[%s]]>', + preg_replace('/\]\]>/', ']]]]><![CDATA[>', strval($data))); + + } + + /** + * split qualified name and return namespace and local part + * + * <code> + * require_once 'XML/Util.php'; + * + * // split qualified tag + * $parts = XML_Util::splitQualifiedName('xslt:stylesheet'); + * </code> + * the returned array will contain two elements: + * <pre> + * array( + * 'namespace' => 'xslt', + * 'localPart' => 'stylesheet' + * ); + * </pre> + * + * @param string $qname qualified tag name + * @param string $defaultNs default namespace (optional) + * + * @return array array containing namespace and local part + * @access public + * @static + */ + function splitQualifiedName($qname, $defaultNs = null) + { + if (strstr($qname, ':')) { + $tmp = explode(':', $qname); + return array( + 'namespace' => $tmp[0], + 'localPart' => $tmp[1] + ); + } + return array( + 'namespace' => $defaultNs, + 'localPart' => $qname + ); + } + + /** + * check, whether string is valid XML name + * + * <p>XML names are used for tagname, attribute names and various + * other, lesser known entities.</p> + * <p>An XML name may only consist of alphanumeric characters, + * dashes, undescores and periods, and has to start with a letter + * or an underscore.</p> + * + * <code> + * require_once 'XML/Util.php'; + * + * // verify tag name + * $result = XML_Util::isValidName('invalidTag?'); + * if (is_a($result, 'PEAR_Error')) { + * print 'Invalid XML name: ' . $result->getMessage(); + * } + * </code> + * + * @param string $string string that should be checked + * + * @return mixed true, if string is a valid XML name, PEAR error otherwise + * @access public + * @static + * @todo support for other charsets + * @todo PEAR CS - unable to avoid 85-char limit on second preg_match + */ + function isValidName($string) + { + // check for invalid chars + if (!preg_match('/^[[:alpha:]_]$/', $string{0})) { + return XML_Util::raiseError('XML names may only start with letter ' + . 'or underscore', XML_UTIL_ERROR_INVALID_START); + } + + // check for invalid chars + if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/', + $string) + ) { + return XML_Util::raiseError('XML names may only contain alphanumeric ' + . 'chars, period, hyphen, colon and underscores', + XML_UTIL_ERROR_INVALID_CHARS); + } + // XML name is valid + return true; + } + + /** + * replacement for XML_Util::raiseError + * + * Avoids the necessity to always require + * PEAR.php + * + * @param string $msg error message + * @param int $code error code + * + * @return PEAR_Error + * @access public + * @static + * @todo PEAR CS - should this use include_once instead? + */ + function raiseError($msg, $code) + { + require_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?> |
