diff --git a/trunk/AUTHORS b/trunk/AUTHORS
new file mode 100644
index 0000000..c0d4294
--- /dev/null
+++ b/trunk/AUTHORS
@@ -0,0 +1,18 @@
+N:Johann Dréo
+P:nojhan
+E:nojhan@gmail.com
+D:2007-11
+C:Code initial, mainteneur
+
+N:Simon Leblanc
+P:Simon
+E:contact@leblanc-simon.eu
+D:2007-12
+C:internationalisation, limitation items RSS, bugfixes, système de cache
+
+N:Guillaume Duhamel
+P:Guill
+E:guillaume.duhamel@gmail.com
+D:2008-05
+C:Galerie de vignettes
+
diff --git a/trunk/COPYING b/trunk/COPYING
new file mode 100644
index 0000000..5b6e7c6
--- /dev/null
+++ b/trunk/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/trunk/HTML/Template/Flexy.php b/trunk/HTML/Template/Flexy.php
new file mode 100644
index 0000000..66b4f18
--- /dev/null
+++ b/trunk/HTML/Template/Flexy.php
@@ -0,0 +1,798 @@
+
+// | Original Author: Wolfram Kriesing |
+// +----------------------------------------------------------------------+
+//
+
+/**
+* @package HTML_Template_Flexy
+*/
+// prevent disaster when used with xdebug!
+@ini_set('xdebug.max_nesting_level', 1000);
+
+/*
+* Global variable - used to store active options when compiling a template.
+*/
+$GLOBALS['_HTML_TEMPLATE_FLEXY'] = array();
+
+// ERRORS:
+
+define('HTML_TEMPLATE_FLEXY_ERROR_SYNTAX',-1); // syntax error in template.
+define('HTML_TEMPLATE_FLEXY_ERROR_INVALIDARGS',-2); // bad arguments to methods.
+define('HTML_TEMPLATE_FLEXY_ERROR_FILE',-2); // file access problem
+
+define('HTML_TEMPLATE_FLEXY_ERROR_RETURN',1); // RETURN ERRORS
+define('HTML_TEMPLATE_FLEXY_ERROR_DIE',8); // FATAL DEATH
+/**
+* A Flexible Template engine - based on simpletemplate
+*
+* @abstract Long Description
+* Have a look at the package description for details.
+*
+* usage:
+* $template = new HTML_Template_Flexy($options);
+* $template->compiler('/name/of/template.html');
+* $data =new StdClass
+* $data->text = 'xxxx';
+* $template->outputObject($data,$elements)
+*
+* Notes:
+* $options can be blank if so, it is read from
+* PEAR::getStaticProperty('HTML_Template_Flexy','options');
+*
+* the first argument to outputObject is an object (which could even be an
+* associateve array cast to an object) - I normally send it the controller class.
+* the seconde argument '$elements' is an array of HTML_Template_Flexy_Elements
+* eg. array('name'=> new HTML_Template_Flexy_Element('',array('value'=>'fred blogs'));
+*
+*
+*
+*
+* @version $Id: Flexy.php,v 1.96 2005/12/20 01:45:06 alan_k Exp $
+*/
+class HTML_Template_Flexy
+{
+
+ /*
+ * @var array $options the options for initializing the template class
+ */
+ var $options = array(
+ 'compileDir' => '', // where do you want to write to.. (defaults to session.save_path)
+ 'templateDir' => '', // where are your templates
+
+ // where the template comes from. ------------------------------------------
+ 'multiSource' => false, // Allow same template to exist in multiple places
+ // So you can have user themes....
+ 'templateDirOrder' => '', // set to 'reverse' to assume that first template
+
+
+ 'debug' => false, // prints a few messages
+
+
+ // compiling conditions ------------------------------------------
+ 'compiler' => 'Flexy', // which compiler to use. (Flexy,Regex, Raw,Xipe)
+ 'forceCompile' => false, // only suggested for debugging
+
+ // regex Compiler ------------------------------------------
+ 'filters' => array(), // used by regex compiler.
+
+ // standard Compiler ------------------------------------------
+ 'nonHTML' => false, // dont parse HTML tags (eg. email templates)
+ 'allowPHP' => false, // allow PHP in template (use true=allow, 'delete' = remove it.)
+
+ 'flexyIgnore' => 0, // turn on/off the tag to element code
+ 'numberFormat' => ",2,'.',','", // default number format {xxx:n} format = eg. 1,200.00
+
+ 'url_rewrite' => '', // url rewriting ability:
+ // eg. "images/:test1/images/,js/:test1/js"
+ // changes href="images/xxx" to href="test1/images/xxx"
+ // and src="js/xxx.js" to src="test1/js/xxx.js"
+
+ 'compileToString' => false, // should the compiler return a string
+ // rather than writing to a file.
+ 'privates' => false, // allow access to _variables (eg. suido privates
+ 'globals' => false, // allow access to _GET/_POST/_REQUEST/GLOBALS/_COOKIES/_SESSION
+
+ 'globalfunctions' => false, // allow GLOBALS.date(#d/m/Y#) to have access to all PHP's methods
+ // warning dont use unless you trust the template authors
+ // exec() becomes exposed.
+
+ // get text/transalation suppport ------------------------------------------
+ // (flexy compiler only)
+ 'locale' => 'en', // works with gettext or File_Gettext
+ 'textdomain' => '', // for gettext emulation with File_Gettext
+ // eg. 'messages' (or you can use the template name.
+ 'textdomainDir' => '', // eg. /var/www/site.com/locale
+ // so the french po file is:
+ // /var/www/site.com/local/fr/LC_MESSAGE/{textdomain}.po
+
+ 'Translation2' => false, // to make Translation2 a provider.
+ // rather than gettext.
+ // set to:
+ // 'Translation2' => array(
+ // 'driver' => 'dataobjectsimple',
+ // 'options' => array()
+ // );
+ // or the slower way..
+ // = as it requires loading the code..
+ //
+ // 'Translation2' => new Translation2('dataobjectsimple','')
+
+
+ 'charset' => 'ISO-8859-1', // charset used with htmlspecialchars to render data.
+ // experimental
+
+ // output options ------------------------------------------
+ 'strict' => false, // All elements in the template must be defined -
+ // makes php E_NOTICE warnings appear when outputing template.
+
+ 'fatalError' => HTML_TEMPLATE_FLEXY_ERROR_DIE, // default behavior is to die on errors in template.
+
+ 'plugins' => array(), // load classes to be made available via the plugin method
+ // eg. = array('Savant') - loads the Savant methods.
+ // = array('MyClass_Plugins' => 'MyClass/Plugins.php')
+ // Class, and where to include it from..
+ );
+ /**
+ * The compiled template filename (Full path)
+ *
+ * @var string
+ * @access public
+ */
+ var $compiledTemplate;
+ /**
+ * The source template filename (Full path)
+ *
+ * @var string
+ * @access public
+ */
+
+
+ var $currentTemplate;
+
+ /**
+ * The getTextStrings Filename
+ *
+ * @var string
+ * @access public
+ */
+ var $getTextStringsFile;
+ /**
+ * The serialized elements array file.
+ *
+ * @var string
+ * @access public
+ */
+ var $elementsFile;
+
+
+ /**
+ * Array of HTML_elements which is displayed on the template
+ *
+ * Technically it's private (eg. only the template uses it..)
+ *
+ *
+ * @var array of HTML_Template_Flexy_Elements
+ * @access private
+ */
+ var $elements = array();
+ /**
+ * Constructor
+ *
+ * Initializes the Template engine, for each instance, accepts options or
+ * reads from PEAR::getStaticProperty('HTML_Template_Flexy','options');
+ *
+ * @access public
+ * @param array $options (Optional)
+ */
+
+ function HTML_Template_Flexy( $options=array() )
+ {
+
+ $baseoptions = array();
+ if (class_exists('PEAR')) {
+ $baseoptions = &PEAR::getStaticProperty('HTML_Template_Flexy','options');
+ }
+ if ($baseoptions ) {
+ foreach( $baseoptions as $key=>$aOption) {
+ $this->options[$key] = $aOption;
+ }
+ }
+
+ foreach( $options as $key=>$aOption) {
+ $this->options[$key] = $aOption;
+ }
+
+ $filters = $this->options['filters'];
+ if (is_string($filters)) {
+ $this->options['filters']= explode(',',$filters);
+ }
+
+ if (is_string($this->options['templateDir'])) {
+ $this->options['templateDir'] = explode(PATH_SEPARATOR,$this->options['templateDir'] );
+ }
+
+
+ }
+
+
+
+
+
+ /**
+ * compile the template
+ *
+ * @access public
+ * @version 01/12/03
+ * @author Wolfram Kriesing
+ * @param string $file relative to the 'templateDir' which you set when calling the constructor
+ * @return boolean true on success. (or string, if compileToString) PEAR_Error on failure..
+ */
+ function compile( $file )
+ {
+ if (!$file) {
+ return $this->raiseError('HTML_Template_Flexy::compile no file selected',
+ HTML_TEMPLATE_FLEXY_ERROR_INVALIDARGS,HTML_TEMPLATE_FLEXY_ERROR_DIE);
+ }
+
+ if (!@$this->options['locale']) {
+ $this->options['locale']='en';
+ }
+
+
+ //Remove the slash if there is one in front, just to be safe.
+ $file = ltrim($file,DIRECTORY_SEPARATOR);
+
+
+ if (strpos($file,'#')) {
+ list($file,$this->options['output.block']) = explode('#', $file);
+ }
+
+ $parts = array();
+ $tmplDirUsed = false;
+
+ // PART A mulitlanguage support: ( part B is gettext support in the engine..)
+ // - user created language version of template.
+ // - compile('abcdef.html') will check for compile('abcdef.en.html')
+ // (eg. when locale=en)
+
+ $this->currentTemplate = false;
+
+ if (preg_match('/(.*)(\.[a-z]+)$/i',$file,$parts)) {
+ $newfile = $parts[1].'.'.$this->options['locale'] .$parts[2];
+ foreach ($this->options['templateDir'] as $tmplDir) {
+ if (@!file_exists($tmplDir . DIRECTORY_SEPARATOR .$newfile)) {
+ continue;
+ }
+ $file = $newfile;
+ $this->currentTemplate = $tmplDir . DIRECTORY_SEPARATOR .$newfile;
+ $tmplDirUsed = $tmplDir;
+ }
+ }
+
+ // look in all the posible locations for the template directory..
+ if ($this->currentTemplate === false) {
+ $dirs = array_unique($this->options['templateDir']);
+ if ($this->options['templateDirOrder'] == 'reverse') {
+ $dirs = array_reverse($dirs);
+ }
+ foreach ($dirs as $tmplDir) {
+ if (!@file_exists($tmplDir . DIRECTORY_SEPARATOR . $file)) {
+ continue;
+ }
+
+
+ if (!$this->options['multiSource'] && ($this->currentTemplate !== false)) {
+ return $this->raiseError("You have more than one template Named {$file} in your paths, found in both".
+ " {$this->currentTemplate } {$tmplDir}" . DIRECTORY_SEPARATOR . $file,
+ HTML_TEMPLATE_FLEXY_ERROR_INVALIDARGS , HTML_TEMPLATE_FLEXY_ERROR_DIE);
+
+ }
+
+ $this->currentTemplate = $tmplDir . DIRECTORY_SEPARATOR . $file;
+ $tmplDirUsed = $tmplDir;
+ }
+ }
+ if ($this->currentTemplate === false) {
+ // check if the compile dir has been created
+ return $this->raiseError("Could not find Template {$file} in any of the directories " .
+ implode(" ",$this->options['templateDir']) ,
+ HTML_TEMPLATE_FLEXY_ERROR_INVALIDARGS, HTML_TEMPLATE_FLEXY_ERROR_DIE);
+ }
+
+
+ // Savant compatible compiler
+
+ if ($this->options['compiler'] == 'Raw') {
+ $this->compiledTemplate = $this->currentTemplate;
+ $this->debug("Using Raw Compiler");
+ return true;
+ }
+
+
+
+
+ // now for the compile target
+
+ //If you are working with mulitple source folders and $options['multiSource'] is set
+ //the template folder will be:
+ // compiled_tempaltes/{templatedir_basename}_{md5_of_dir}/
+
+
+ $compileSuffix = ((count($this->options['templateDir']) > 1) && $this->options['multiSource']) ?
+ DIRECTORY_SEPARATOR .basename($tmplDirUsed) . '_' .md5($tmplDirUsed) : '';
+
+
+ $compileDest = @$this->options['compileDir'];
+
+ $isTmp = false;
+ // Use a default compile directory if one has not been set.
+ if (!@$compileDest) {
+ // Use session.save_path + 'compiled_templates_' + md5(of sourcedir)
+ $compileDest = ini_get('session.save_path') . DIRECTORY_SEPARATOR . 'flexy_compiled_templates';
+ if (!file_exists($compileDest)) {
+ require_once 'System.php';
+ System::mkdir(array('-p',$compileDest));
+ }
+ $isTmp = true;
+
+ }
+
+
+
+ // we generally just keep the directory structure as the application uses it,
+ // so we dont get into conflict with names
+ // if we have multi sources we do md5 the basedir..
+
+
+ $base = $compileDest . $compileSuffix . DIRECTORY_SEPARATOR .$file;
+ $fullFile = $this->compiledTemplate = $base .'.'.$this->options['locale'].'.php';
+ $this->getTextStringsFile = $base .'.gettext.serial';
+ $this->elementsFile = $base .'.elements.serial';
+ if (isset($this->options['output.block'])) {
+ $this->compiledTemplate .= '#'.$this->options['output.block'];
+ }
+
+ $recompile = false;
+
+ $isuptodate = file_exists($this->compiledTemplate) ?
+ (filemtime($this->currentTemplate) == filemtime( $this->compiledTemplate)) : 0;
+
+ if( @$this->options['forceCompile'] || !$isuptodate ) {
+ $recompile = true;
+ } else {
+ $this->debug("File looks like it is uptodate.");
+ return true;
+ }
+
+
+
+
+ if( !@is_dir($compileDest) || !is_writeable($compileDest)) {
+ require_once 'System.php';
+
+ System::mkdir(array('-p',$compileDest));
+ }
+ if( !@is_dir($compileDest) || !is_writeable($compileDest)) {
+ return $this->raiseError( "can not write to 'compileDir', which is '$compileDest' ".
+ "Please give write and enter-rights to it",
+ HTML_TEMPLATE_FLEXY_ERROR_FILE, HTML_TEMPLATE_FLEXY_ERROR_DIE);
+ }
+
+ if (!file_exists(dirname($this->compiledTemplate))) {
+ require_once 'System.php';
+ System::mkdir(array('-p','-m', 0770, dirname($this->compiledTemplate)));
+ }
+
+ // Compile the template in $file.
+
+ require_once 'HTML/Template/Flexy/Compiler.php';
+ $compiler = HTML_Template_Flexy_Compiler::factory($this->options);
+ $ret = $compiler->compile($this);
+ if (is_a($ret,'PEAR_Error')) {
+ return $this->raiseError('HTML_Template_Flexy fatal error:' .$ret->message,
+ $ret->code, HTML_TEMPLATE_FLEXY_ERROR_DIE);
+ }
+ return $ret;
+
+ //return $this->$method();
+
+ }
+
+ /**
+ * compiles all templates
+ * Used for offline batch compilation (eg. if your server doesn't have write access to the filesystem).
+ *
+ * @access public
+ * @author Alan Knowles
+ *
+ */
+ function compileAll($dir = '',$regex='/.html$/')
+ {
+
+ require_once 'HTML/Template/Flexy/Compiler.php';
+ $c = new HTML_Template_Flexy_Compiler;
+ $c->compileAll($this,$dir,$regex);
+ }
+
+ /**
+ * Outputs an object as $t
+ *
+ * for example the using simpletags the object's variable $t->test
+ * would map to {test}
+ *
+ * @version 01/12/14
+ * @access public
+ * @author Alan Knowles
+ * @param object to output
+ * @param array HTML_Template_Flexy_Elements (or any object that implements toHtml())
+ * @return none
+ */
+
+
+ function outputObject(&$t,$elements=array())
+ {
+ if (!is_array($elements)) {
+ return $this->raiseError(
+ 'second Argument to HTML_Template_Flexy::outputObject() was an '.gettype($elements) . ', not an array',
+ HTML_TEMPLATE_FLEXY_ERROR_INVALIDARGS ,HTML_TEMPLATE_FLEXY_ERROR_DIE);
+ }
+ if (@$this->options['debug']) {
+ echo "output $this->compiledTemplate ";
+ }
+
+ // this may disappear later it's a Backwards Compatibility fudge to try
+ // and deal with the first stupid design decision to not use a second argument
+ // to the method.
+
+ if (count($this->elements) && !count($elements)) {
+ $elements = $this->elements;
+ }
+ // end depreciated code
+
+
+ $this->elements = $this->getElements();
+
+ // Overlay values from $elements to $this->elements (which is created from the template)
+ // Remove keys with no corresponding value.
+ foreach($elements as $k=>$v) {
+ // Remove key-value pair from $this->elements if hasn't a value in $elements.
+ if (!$v) {
+ unset($this->elements[$k]);
+ }
+ // Add key-value pair to $this->$elements if it's not there already.
+ if (!isset($this->elements[$k])) {
+ $this->elements[$k] = $v;
+ continue;
+ }
+ // Call the clever element merger - that understands form values and
+ // how to display them...
+ $this->elements[$k] = $this->mergeElement($this->elements[$k] ,$v);
+ }
+ //echo '
'; print_r(array($elements,$this->elements));
+
+
+ // we use PHP's error handler to hide errors in the template.
+ // use $options['strict'] - if you want to force declaration of
+ // all variables in the template
+
+
+ $_error_reporting = false;
+ if (!$this->options['strict']) {
+ $_error_reporting = error_reporting(E_ALL ^ E_NOTICE);
+ }
+ if (!is_readable($this->compiledTemplate)) {
+ return $this->raiseError( "Could not open the template: '{$this->compiledTemplate}' ".
+ "Please check the file permissions on the directory and file ",
+ HTML_TEMPLATE_FLEXY_ERROR_FILE, HTML_TEMPLATE_FLEXY_ERROR_DIE);
+ }
+
+ // are we using the assign api!
+
+ if (isset($this->assign)) {
+ if (!$t) {
+ $t = (object) $this->assign->variables;
+ }
+ extract($this->assign->variables);
+ foreach(array_keys($this->assign->references) as $_k) {
+ $$_k = &$this->assign->references[$_k];
+ }
+ }
+ // used by Flexy Elements etc..
+ $GLOBALS['_HTML_TEMPLATE_FLEXY']['options'] = $this->options;
+
+ include($this->compiledTemplate);
+
+ // Return the error handler to its previous state.
+
+ if ($_error_reporting !== false) {
+ error_reporting($_error_reporting);
+ }
+ }
+ /**
+ * Outputs an object as $t, buffers the result and returns it.
+ *
+ * See outputObject($t) for more details.
+ *
+ * @version 01/12/14
+ * @access public
+ * @author Alan Knowles
+ * @param object object to output as $t
+ * @return string - result
+ */
+ function bufferedOutputObject(&$t,$elements=array())
+ {
+ ob_start();
+ $this->outputObject($t,$elements);
+ $data = ob_get_contents();
+ ob_end_clean();
+ return $data;
+ }
+ /**
+ * static version which does new, compile and output all in one go.
+ *
+ * See outputObject($t) for more details.
+ *
+ * @version 01/12/14
+ * @access public
+ * @author Alan Knowles
+ * @param object object to output as $t
+ * @param filename of template
+ * @return string - result
+ */
+ function &staticQuickTemplate($file,&$t)
+ {
+ $template = new HTML_Template_Flexy;
+ $template->compile($file);
+ $template->outputObject($t);
+ }
+
+ /**
+ * if debugging is on, print the debug info to the screen
+ *
+ * @access public
+ * @author Alan Knowles
+ * @param string $string output to display
+ * @return none
+ */
+ function debug($string)
+ {
+
+ if (is_a($this,'HTML_Template_Flexy')) {
+ if (!$this->options['debug']) {
+ return;
+ }
+ } else if (!@$GLOBALS['_HTML_TEMPLATE_FLEXY']['debug']) {
+ return;
+ }
+ echo "
FLEXY DEBUG: $string
";
+
+ }
+
+ /**
+ * A general Utility method that merges HTML_Template_Flexy_Elements
+ * Static method - no native debug avaiable..
+ *
+ * @param HTML_Template_Flexy_Element $original (eg. from getElements())
+ * @param HTML_Template_Flexy_Element $new (with data to replace/merge)
+ * @return HTML_Template_Flexy_Element the combined/merged data.
+ * @static
+ * @access public
+ */
+
+ function mergeElement($original,$new)
+ {
+
+ // no original - return new
+ if (!$original) {
+ return $new;
+ }
+ // no new - return original
+ if (!$new) {
+ return $original;
+ }
+ // If the properties of $original differ from those of $new and
+ // they are set on $new, set them to $new's. Otherwise leave them
+ // as they are.
+
+ if ($new->tag && ($new->tag != $original->tag)) {
+ $original->tag = $new->tag;
+ }
+
+ if ($new->override !== false) {
+ $original->override = $new->override;
+ }
+
+ if (count($new->children)) {
+ //echo "
COPY CHILDREN"; print_r($from->children);
+ $original->children = $new->children;
+ }
+
+ if (is_array($new->attributes)) {
+
+ foreach ($new->attributes as $key => $value) {
+ $original->attributes[$key] = $value;
+ }
+ }
+ // originals never have prefixes or suffixes..
+ $original->prefix = $new->prefix;
+ $original->suffix = $new->suffix;
+
+ if ($new->value !== null) {
+ $original->setValue($new->value);
+ }
+
+ return $original;
+
+ }
+
+
+ /**
+ * Get an array of elements from the template
+ *
+ * All
\ No newline at end of file
diff --git a/trunk/HTML/Template/tests/test_function.html.phpt b/trunk/HTML/Template/tests/test_function.html.phpt
new file mode 100644
index 0000000..e00a6d7
--- /dev/null
+++ b/trunk/HTML/Template/tests/test_function.html.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Template Test: function.html
+--FILE--
+this is the contents of test1
+
+Dont forget that php strips line breaks!
+options['strict'] || (is_array($t->list) || is_object($t->list))) foreach($t->list as $i => $j) {?>
+ :
+
+
+]]>
+
+===With data file: looping.html===
+
+
+
Looping
+
+
+
a loop
+
a loop with 2 vars
+
+Bug #84
+
+ 0:1 1:2 2:3 3:4
+
+
+
+
+
HTML tags example using foreach="loop,a,b" or the tr
+
+
+
+
HTML tags example using foreach="loop,a" or the tr using a highlight class.
+
+
+
+
HTML tags example using foreach="loop,a,b" or the tr
+
+
+
+
Looping in CDATA
+Dont forget that php strips line breaks!
+
\ No newline at end of file
diff --git a/trunk/HTML/Template/tests/test_methods.html.phpt b/trunk/HTML/Template/tests/test_methods.html.phpt
new file mode 100644
index 0000000..4d996eb
--- /dev/null
+++ b/trunk/HTML/Template/tests/test_methods.html.phpt
@@ -0,0 +1,123 @@
+--TEST--
+Template Test: methods.html
+--FILE--
+Methods
+
Calling a method options['strict'] || (isset($t->a) && method_exists($t->a,'helloWorld'))) echo htmlspecialchars($t->a->helloWorld());?>
+
or options['strict'] || (isset($t) && method_exists($t,'includeBody'))) echo $t->includeBody();?>
+ * @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:
+ *
+ * // global error stack
+ * $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
+ * // local error stack
+ * $local_stack = new PEAR_ErrorStack('MyPackage');
+ *
+ * @author Greg Beaver
+ * @version 1.5.0a1
+ * @package PEAR_ErrorStack
+ * @category Debugging
+ * @copyright 2004-2006 Greg Beaver
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: ErrorStack.php,v 1.25 2006/09/22 03:13:17 cellog Exp $
+ * @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 ' ';
+ } 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:
+ *
+ *
+ * array(
+ * 'code' => $code,
+ * 'params' => $params,
+ * 'package' => $this->_package,
+ * 'level' => $level,
+ * 'time' => time(),
+ * 'context' => $context,
+ * 'message' => $msg,
+ * //['repackage' => $err] repackaged error array/Exception class
+ * );
+ *
+ *
+ * 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);
+ $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+)\) : (.*?)$;', $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:
+ *
+ *
+ * $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
+ *
+ *
+ * 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:
+ *
';
+ 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/trunk/PEAR/Frontend.php b/trunk/PEAR/Frontend.php
new file mode 100644
index 0000000..b8e4530
--- /dev/null
+++ b/trunk/PEAR/Frontend.php
@@ -0,0 +1,186 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Frontend.php,v 1.9 2006/03/03 13:13:07 pajoye Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * 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
+ *
+ * Note that frontend classes must implement userConfirm(), and shoul implement
+ * displayFatalError() and outputData()
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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'];
+ } else {
+ $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 (method_exists($obj, 'userConfirm')) {
+ $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj;
+ $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass;
+ return $obj;
+ } else {
+ $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;
+ }
+ // quick test to see if this class implements a few of the most
+ // important frontend methods
+ if (method_exists($uiobject, 'userConfirm')) {
+ $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject;
+ $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject);
+ return $uiobject;
+ } else {
+ $err = PEAR::raiseError("not a value frontend class: (" . get_class($uiobject)
+ . ')');
+ return $err;
+ }
+ }
+
+ /**
+ * @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 (!$test) { // support wrappers like phar (realpath just don't work with them)
+ $test = $include . DIRECTORY_SEPARATOR . $path;
+ }
+ if (file_exists($test) && is_readable($test)) {
+ 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;
+ }
+
+ function log($msg, $append_crlf = true)
+ {
+ }
+}
+?>
\ No newline at end of file
diff --git a/trunk/PEAR/Frontend/CLI.php b/trunk/PEAR/Frontend/CLI.php
new file mode 100644
index 0000000..f91fa77
--- /dev/null
+++ b/trunk/PEAR/Frontend/CLI.php
@@ -0,0 +1,764 @@
+
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: CLI.php,v 1.64 2006/04/25 02:57:57 cellog Exp $
+ * @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
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Frontend_CLI extends PEAR_Frontend
+{
+ // {{{ properties
+
+ /**
+ * 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' => '',
+ );
+
+ // }}}
+
+ // {{{ constructor
+
+ 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) {
+ // XXX can use ncurses extension here, if available
+ 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
+ }
+ }
+
+ // }}}
+
+ // {{{ displayLine(text)
+
+ function displayLine($text)
+ {
+ trigger_error("PEAR_Frontend_CLI::displayLine deprecated", E_USER_ERROR);
+ }
+
+ function _displayLine($text)
+ {
+ print "$this->lp$text\n";
+ }
+
+ // }}}
+ // {{{ display(text)
+
+ function display($text)
+ {
+ trigger_error("PEAR_Frontend_CLI::display deprecated", E_USER_ERROR);
+ }
+
+ function _display($text)
+ {
+ print $text;
+ }
+
+ // }}}
+ // {{{ displayError(eobj)
+
+ /**
+ * @param object PEAR_Error object
+ */
+ function displayError($eobj)
+ {
+ return $this->_displayLine($eobj->getMessage());
+ }
+
+ // }}}
+ // {{{ displayFatalError(eobj)
+
+ /**
+ * @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();
+ } elseif (function_exists('debug_backtrace')) {
+ $trace = debug_backtrace();
+ $raised = false;
+ foreach ($trace as $i => $frame) {
+ if (!$raised) {
+ if (isset($frame['class']) && strtolower($frame['class']) ==
+ 'pear' && strtolower($frame['function']) == 'raiseerror') {
+ $raised = true;
+ } else {
+ continue;
+ }
+ }
+ if (!isset($frame['class'])) {
+ $frame['class'] = '';
+ }
+ if (!isset($frame['type'])) {
+ $frame['type'] = '';
+ }
+ if (!isset($frame['function'])) {
+ $frame['function'] = '';
+ }
+ if (!isset($frame['line'])) {
+ $frame['line'] = '';
+ }
+ $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]");
+ }
+ }
+ }
+ }
+ exit(1);
+ }
+
+ // }}}
+ // {{{ displayHeading(title)
+
+ function displayHeading($title)
+ {
+ trigger_error("PEAR_Frontend_CLI::displayHeading deprecated", E_USER_ERROR);
+ }
+
+ function _displayHeading($title)
+ {
+ print $this->lp.$this->bold($title)."\n";
+ print $this->lp.str_repeat("=", strlen($title))."\n";
+ }
+
+ // }}}
+
+ /**
+ * 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');
+ } else {
+ $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)) {
+ 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;
+ }
+ }
+ } else {
+ 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 = array();
+ $prompts = $types = array();
+ foreach ($params as $param) {
+ $prompts[$param['name']] = $param['prompt'];
+ $types[$param['name']] = $param['type'];
+ if (isset($param['default'])) {
+ $answers[$param['name']] = $param['default'];
+ } else {
+ $answers[$param['name']] = '';
+ }
+ }
+ $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;
+ }
+ // {{{ userDialog(prompt, [type], [default])
+
+ function userDialog($command, $prompts, $types = array(), $defaults = array(),
+ $screensize = 20)
+ {
+ if (!is_array($prompts)) {
+ return array();
+ }
+ $testprompts = array_keys($prompts);
+ $result = $defaults;
+ if (!defined('STDIN')) {
+ $fp = fopen('php://stdin', 'r');
+ } else {
+ $fp = STDIN;
+ }
+ while (true) {
+ $descLength = max(array_map('strlen', $prompts));
+ $descFormat = "%-{$descLength}s";
+ $last = count($prompts);
+
+ $i = 0;
+ foreach ($prompts as $n => $var) {
+ printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], isset($result[$n]) ?
+ $result[$n] : null);
+ }
+
+ print "\n1-$last, 'all', 'abort', or Enter to continue: ";
+ $tmp = trim(fgets($fp, 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($fp, 1024));
+ if (trim($tmp) !== '') {
+ $result[$var] = trim($tmp);
+ }
+ } elseif ($tmp == 'all') {
+ foreach ($prompts as $var => $desc) {
+ $current = $result[$var];
+ print "$desc [$current] : ";
+ $tmp = trim(fgets($fp, 1024));
+ if (trim($tmp) !== '') {
+ $result[$var] = trim($tmp);
+ }
+ }
+ }
+ }
+ if (!defined('STDIN')) {
+ fclose($fp);
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ userConfirm(prompt, [default])
+
+ 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;
+ }
+
+ // }}}
+ // {{{ startTable([params])
+
+ function startTable($params = array())
+ {
+ trigger_error("PEAR_Frontend_CLI::startTable deprecated", E_USER_ERROR);
+ }
+
+ 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;
+ }
+
+ // }}}
+ // {{{ tableRow(columns, [rowparams], [colparams])
+
+ function tableRow($columns, $rowparams = array(), $colparams = array())
+ {
+ trigger_error("PEAR_Frontend_CLI::tableRow deprecated", E_USER_ERROR);
+ }
+
+ function _tableRow($columns, $rowparams = array(), $colparams = array())
+ {
+ $highest = 1;
+ for ($i = 0; $i < sizeof($columns); $i++) {
+ $col = &$columns[$i];
+ if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) {
+ $col = wordwrap($col, $colparams[$i]['wrap'], "\n", 0);
+ }
+ if (strpos($col, "\n") !== false) {
+ $multiline = explode("\n", $col);
+ $w = 0;
+ foreach ($multiline as $n => $line) {
+ if (strlen($line) > $w) {
+ $w = strlen($line);
+ }
+ }
+ $lines = sizeof($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 (sizeof($columns) > $this->params['ncols']) {
+ $this->params['ncols'] = sizeof($columns);
+ }
+ $new_row = array(
+ 'data' => $columns,
+ 'height' => $highest,
+ 'rowparams' => $rowparams,
+ 'colparams' => $colparams,
+ );
+ $this->params['table_data'][] = $new_row;
+ }
+
+ // }}}
+ // {{{ endTable()
+
+ function endTable()
+ {
+ trigger_error("PEAR_Frontend_CLI::endTable deprecated", E_USER_ERROR);
+ }
+
+ 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 < sizeof($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 < sizeof($data); $c++) {
+ $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]);
+ if (sizeof($rowlines[$c]) < $height) {
+ $rowlines[$c] = array_pad($rowlines[$c], $height, '');
+ }
+ }
+ } else {
+ for ($c = 0; $c < sizeof($data); $c++) {
+ $rowlines[$c] = array($data[$c]);
+ }
+ }
+ for ($r = 0; $r < $height; $r++) {
+ $rowtext = '';
+ for ($c = 0; $c < sizeof($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);
+ }
+ }
+
+ // }}}
+ // {{{ outputData()
+
+ 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 (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($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)));
+ }
+
+ foreach($data['data'] as $category) {
+ foreach($category as $pkg) {
+ $this->_tableRow($pkg, null, array(1 => array('wrap' => 55)));
+ }
+ };
+ $this->_endTable();
+ break;
+ case 'list-all':
+ $this->_startTable($data);
+ if (isset($data['headline']) && is_array($data['headline'])) {
+ $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
+ }
+
+ foreach($data['data'] as $category) {
+ foreach($category as $pkg) {
+ unset($pkg[4]);
+ unset($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] = "";
+ }
+ $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);
+ }
+ foreach($data['data'] as $row) {
+ $this->_tableRow($row, null, $opts);
+ }
+ $this->_endTable();
+ } else {
+ $this->_displayLine($data);
+ }
+ }
+ }
+ }
+
+ // }}}
+ // {{{ log(text)
+
+
+ function log($text, $append_crlf = true)
+ {
+ if ($append_crlf) {
+ return $this->_displayLine($text);
+ }
+ return $this->_display($text);
+ }
+
+
+ // }}}
+ // {{{ bold($text)
+
+ function bold($text)
+ {
+ if (empty($this->term['bold'])) {
+ return strtoupper($text);
+ }
+ return $this->term['bold'] . $text . $this->term['normal'];
+ }
+
+ // }}}
+}
+
+?>
diff --git a/trunk/PEAR/Installer.php b/trunk/PEAR/Installer.php
new file mode 100644
index 0000000..0d50f60
--- /dev/null
+++ b/trunk/PEAR/Installer.php
@@ -0,0 +1,1645 @@
+
+ * @author Tomas V.V. Cox
+ * @author Martin Jansen
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Installer.php,v 1.237 2006/09/25 23:01:05 cellog Exp $
+ * @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
+ * @author Tomas V.V. Cox
+ * @author Martin Jansen
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Installer extends PEAR_Downloader
+{
+ // {{{ properties
+
+ /** 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();
+
+ // }}}
+
+ // {{{ constructor
+
+ /**
+ * 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));
+ }
+ }
+
+ // {{{ _deletePackageFiles()
+
+ /**
+ * 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;
+ }
+
+ // }}}
+ // {{{ _installFile()
+
+ /**
+ * @param string filename
+ * @param array attributes from 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 '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;
+ case 'src':
+ case 'extsrc':
+ $this->source_files++;
+ return;
+ 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 (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_]+$/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);
+ } else {
+ 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) {
+ if ($atts['role'] == 'script') {
+ $mode = 0777 & ~(int)octdec($this->config->get('umask'));
+ $this->log(3, "+ chmod +x $dest_file");
+ } else {
+ $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+ }
+ $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");
+ }
+ }
+ }
+ // }}}
+ $this->addFileOperation("rename", array($dest_file, $final_dest_file,
+ $atts['role'] == 'ext'));
+ }
+ // Store the full path where the file was installed for easy unistall
+ $this->addFileOperation("installed_as", array($file, $installed_as,
+ $save_destdir, dirname(substr($installedas_dest_file, strlen($save_destdir)))));
+
+ //$this->log(2, "installed: $dest_file");
+ return PEAR_INSTALLER_OK;
+ }
+
+ // }}}
+ // {{{ _installFile2()
+
+ /**
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param string filename
+ * @param array attributes from tag in package.xml
+ * @param string path to install the file in
+ * @param array options from command-line
+ * @access private
+ */
+ function _installFile2(&$pkg, $file, $atts, $tmp_path, $options)
+ {
+ if (!isset($this->_registry)) {
+ $this->_registry = &$this->config->getRegistry();
+ }
+
+ $channel = $pkg->getChannel();
+ // {{{ assemble the destination paths
+ if (!in_array($atts['attribs']['role'],
+ PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
+ 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;
+ } else {
+ list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
+ }
+ $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)) {
+ if (strtolower($md5sum) == strtolower($attribs['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);
+ } else {
+ 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) {
+ 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'));
+ }
+ $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");
+ }
+ }
+ }
+ // }}}
+ $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
+ }
+ // Store the full path where the file was installed for easy uninstall
+ $this->addFileOperation("installed_as", array($file, $installed_as,
+ $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
+
+ //$this->log(2, "installed: $dest_file");
+ return PEAR_INSTALLER_OK;
+ }
+
+ // }}}
+ // {{{ addFileOperation()
+
+ /**
+ * 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);
+ }
+
+ // }}}
+ // {{{ startFileTransaction()
+
+ function startFileTransaction($rollback_in_case = false)
+ {
+ if (count($this->file_operations) && $rollback_in_case) {
+ $this->rollbackFileTransaction();
+ }
+ $this->file_operations = array();
+ }
+
+ // }}}
+ // {{{ commitFileTransaction()
+
+ function commitFileTransaction()
+ {
+ $n = count($this->file_operations);
+ $this->log(2, "about to commit $n file operations");
+ // {{{ first, check permissions and such manually
+ $errors = array();
+ foreach ($this->file_operations as $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);
+ }
+ }
+ }
+ break;
+ }
+
+ }
+ // }}}
+ $m = sizeof($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 $tr) {
+ list($type, $data) = $tr;
+ switch ($type) {
+ case 'backup':
+ 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':
+ if (file_exists($data[1])) {
+ $test = @unlink($data[1]);
+ } else {
+ $test = 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]) && $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;
+ }
+
+ // }}}
+ // {{{ rollbackFileTransaction()
+
+ 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')) {
+ @unlink($data[0]);
+ @copy($data[0] . '.bak', $data[0]);
+ $this->log(3, "+ restore $data[0] from $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();
+ }
+
+ // }}}
+ // {{{ mkDirHier($dir)
+
+ function mkDirHier($dir)
+ {
+ $this->addFileOperation('mkdir', array($dir));
+ return parent::mkDirHier($dir);
+ }
+
+ // }}}
+ // {{{ download()
+
+ /**
+ * 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:
+ *
+ *
+ * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
+ * 'info' => array() // parsed package.xml
+ * );
+ *
+ * @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;
+ }
+
+ // }}}
+ // {{{ _parsePackageXml()
+
+ function _parsePackageXml(&$descfile, &$tmpdir)
+ {
+ if (substr($descfile, -4) == '.xml') {
+ $tmpdir = false;
+ } else {
+ // {{{ Decompress pack in tmp dir -------------------------------------
+
+ // To allow relative package file names
+ $descfile = realpath($descfile);
+
+ if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
+ return $tmpdir;
+ }
+ $this->log(3, '+ tmp dir created at ' . $tmpdir);
+ // }}}
+ }
+ // Parse xml file -----------------------------------------------
+ $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir);
+ 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');
+ } else {
+ $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;
+ }
+
+ // {{{ install()
+
+ /**
+ * 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();
+ $tmpdir = dirname($descfile);
+ } else {
+ $descfile = $pkgfile;
+ $tmpdir = '';
+ if (PEAR::isError($pkg = &$this->_parsePackageXml($descfile, $tmpdir))) {
+ return $pkg;
+ }
+ }
+
+ if (realpath($descfile) != realpath($pkgfile)) {
+ $tar = new Archive_Tar($pkgfile);
+ if (!$tar->extract($tmpdir)) {
+ return $this->raiseError("unable to unpack $pkgfile");
+ }
+ }
+
+ $pkgname = $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);
+ $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' ? $pkgname : array($channel, $pkgname);
+ $instfilelist = $pkg->getInstallationFileList(true);
+ if (PEAR::isError($instfilelist)) {
+ return $instfilelist;
+ }
+ $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())) {
+ unset($test[$file]);
+ unset($parentreg['filelist'][$file]);
+ }
+ } else {
+ if (strtolower($param->getChannel()) != 'pear.php.net') {
+ continue;
+ }
+ if (strtolower($info) == strtolower($param->getPackage())) {
+ unset($test[$file]);
+ unset($parentreg['filelist'][$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 (sizeof($test)) {
+ $msg = "$channel/$pkgname: 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);
+ } else {
+ if (!isset($options['soft'])) {
+ $this->log(0, "WARNING: $msg");
+ }
+ }
+ }
+ }
+ }
+ // }}}
+
+ $this->startFileTransaction();
+
+ if (empty($options['upgrade']) && empty($options['soft'])) {
+ // checks to do only when installing new packages
+ if ($channel == 'pecl.php.net') {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ if (!$test) {
+ $test = $installregistry->packageExists($pkgname, 'pear.php.net');
+ }
+ } else {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ }
+ if (empty($options['force']) && $test) {
+ return $this->raiseError("$channel/$pkgname is already installed");
+ }
+ } else {
+ $usechannel = $channel;
+ if ($channel == 'pecl.php.net') {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ if (!$test) {
+ $test = $installregistry->packageExists($pkgname, 'pear.php.net');
+ $usechannel = 'pear.php.net';
+ }
+ } else {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ }
+ if ($test) {
+ $v1 = $installregistry->packageInfo($pkgname, '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)");
+ }
+ if (empty($options['register-only'])) {
+ // when upgrading, remove old release's files first:
+ if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
+ true))) {
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError($err);
+ } else {
+ 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");
+ }
+ }
+
+ $tmp_path = dirname($descfile);
+ if (substr($pkgfile, -4) != '.xml') {
+ $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
+ }
+
+ $this->configSet('default_channel', $channel);
+ // {{{ install files
+
+ $ver = $pkg->getPackagexmlVersion();
+ if (version_compare($ver, '2.0', '>=')) {
+ $filelist = $pkg->getInstallationFilelist();
+ } else {
+ $filelist = $pkg->getFileList();
+ }
+ if (PEAR::isError($filelist)) {
+ return $filelist;
+ }
+ $pkg->resetFilelist();
+ $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
+ 'version', $pkg->getChannel()));
+ foreach ($filelist as $file => $atts) {
+ if ($pkg->getPackagexmlVersion() == '1.0') {
+ $this->expectError(PEAR_INSTALLER_FAILED);
+ $res = $this->_installFile($file, $atts, $tmp_path, $options);
+ $this->popExpect();
+ } else {
+ $this->expectError(PEAR_INSTALLER_FAILED);
+ $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $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);
+ } else {
+ if (!isset($options['soft'])) {
+ $this->log(0, "Warning: " . $res->getMessage());
+ }
+ }
+ }
+ if ($res == PEAR_INSTALLER_OK) {
+ // Register files that were installed
+ $pkg->installedFile($file, $atts);
+ }
+ }
+ // }}}
+
+ // {{{ compile and install source files
+ if ($this->source_files > 0 && empty($options['nobuild'])) {
+ if (PEAR::isError($err =
+ $this->_compileSourceFiles($savechannel, $pkg))) {
+ 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);
+ }
+ // }}}
+
+ $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
+ $usechannel = $channel;
+ if ($channel == 'pecl.php.net') {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ if (!$test) {
+ $test = $installregistry->packageExists($pkgname, 'pear.php.net');
+ $usechannel = 'pear.php.net';
+ }
+ } else {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ }
+ if (!empty($options['force']) && $test) {
+ $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
+ $installregistry->deletePackage($pkgname, $usechannel);
+ }
+ $ret = $installregistry->addPackage2($pkg);
+ } else {
+ $usechannel = $channel;
+ if ($channel == 'pecl.php.net') {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ if (!$test) {
+ $test = $installregistry->packageExists($pkgname, 'pear.php.net');
+ $usechannel = 'pear.php.net';
+ }
+ } else {
+ $test = $installregistry->packageExists($pkgname, $channel);
+ }
+ // new: upgrade installs a package if it isn't installed
+ if (!$test) {
+ $ret = $installregistry->addPackage2($pkg);
+ } else {
+ if ($usechannel != $channel) {
+ $installregistry->deletePackage($pkgname, $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/$pkgname 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);
+ }
+
+ // }}}
+
+ // {{{ _compileSourceFiles()
+ /**
+ * @param string
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ */
+ function _compileSourceFiles($savechannel, &$filelist)
+ {
+ 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'));
+ 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);
+ if ($copyto != $dest) {
+ $this->log(1, "Installing '$dest' as '$copyto'");
+ } else {
+ $this->log(1, "Installing '$dest'");
+ }
+ $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");
+ 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)");
+ }
+ }
+ $this->addFileOperation('rename', array($ext['file'], $copyto));
+ }
+
+ if ($filelist->getPackageXmlVersion() == '1.0') {
+ $filelist->installedFile($bn, 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'],
+ ));
+ } else {
+ $filelist->installedFile($bn, array('attribs' => 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'],
+ )));
+ }
+ }
+ }
+
+ // }}}
+ function &getUninstallPackages()
+ {
+ return $this->_downloadedPackages;
+ }
+ // {{{ uninstall()
+
+ /**
+ * 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
+ * - nodeps: do not process dependencies of other packages to ensure
+ * uninstallation does not break things
+ */
+ function uninstall($package, $options = array())
+ {
+ if (isset($options['installroot'])) {
+ $this->config->setInstallRoot($options['installroot']);
+ $this->installroot = '';
+ } else {
+ $this->config->setInstallRoot('');
+ $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);
+ } else {
+ 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;
+ // {{{ 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);
+ } else {
+ 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");
+ } elseif (!isset($options['soft'])) {
+ $this->log(0, 'WARNING: uninstall failed');
+ }
+ } else {
+ $this->startFileTransaction();
+ if ($dirtree = $pkg->getDirTree()) {
+ // attempt to delete empty directories
+ uksort($dirtree, array($this, '_sortDirs'));
+ foreach($dirtree as $dir => $notused) {
+ $this->addFileOperation('rmdir', array($dir));
+ }
+ } else {
+ $this->configSet('default_channel', $savechannel);
+ return $this->_registry->deletePackage($package, $channel);
+ }
+ if (!$this->commitFileTransaction()) {
+ $this->rollbackFileTransaction();
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError("uninstall failed");
+ } elseif (!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;
+ }
+
+ // }}}
+ // {{{ _sortDirs()
+ function _sortDirs($a, $b)
+ {
+ if (strnatcmp($a, $b) == -1) return 1;
+ if (strnatcmp($a, $b) == 1) return -1;
+ return 0;
+ }
+
+ // }}}
+
+ // {{{ _buildCallback()
+
+ function _buildCallback($what, $data)
+ {
+ if (($what == 'cmdoutput' && $this->debug > 1) ||
+ ($what == 'output' && $this->debug > 0)) {
+ $this->ui->outputData(rtrim($data), 'build');
+ }
+ }
+
+ // }}}
+}
+
+// {{{ md5_file() utility function
+if (!function_exists("md5_file")) {
+ function md5_file($filename) {
+ if (!$fd = @fopen($file, 'r')) {
+ return false;
+ }
+ fclose($fd);
+ return md5(file_get_contents($filename));
+ }
+}
+// }}}
+
+?>
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role.php b/trunk/PEAR/Installer/Role.php
new file mode 100644
index 0000000..939d3b2
--- /dev/null
+++ b/trunk/PEAR/Installer/Role.php
@@ -0,0 +1,250 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Role.php,v 1.15 2006/05/12 02:38:58 cellog Exp $
+ * @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';
+//$GLOBALS['_PEAR_INSTALLER_ROLES'] = array();
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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
+ * @access private
+ * @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($info['config_vars']);
+ }
+ }
+
+ /**
+ * @param PEAR_PackageFile_v2
+ * @param string role name
+ * @param PEAR_Config
+ * @return PEAR_Installer_Role_Common
+ * @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
+ * @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
+ * @static
+ */
+ function getInstallableRoles($clear = false)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+ static $ret;
+ if ($clear) {
+ unset($ret);
+ }
+ if (!isset($ret)) {
+ 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
+ * @static
+ */
+ function getBaseinstallRoles($clear = false)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+ static $ret;
+ if ($clear) {
+ unset($ret);
+ }
+ if (!isset($ret)) {
+ 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
+ * @static
+ */
+ function getPhpRoles($clear = false)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+ static $ret;
+ if ($clear) {
+ unset($ret);
+ }
+ if (!isset($ret)) {
+ 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
+ * @access public
+ * @static
+ */
+ function registerRoles($dir = null)
+ {
+ $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");
+ }
+ $dp = @opendir($dir);
+ if (empty($dp)) {
+ return PEAR::raiseError("registerRoles: opendir($dir) failed");
+ }
+ while ($entry = readdir($dp)) {
+ if ($entry{0} == '.' || substr($entry, -4) != '.xml') {
+ continue;
+ }
+ $class = "PEAR_Installer_Role_".substr($entry, 0, -4);
+ // List of roles
+ if (empty($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/trunk/PEAR/Installer/Role/Common.php b/trunk/PEAR/Installer/Role/Common.php
new file mode 100644
index 0000000..5c6121d
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Common.php
@@ -0,0 +1,180 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Common.php,v 1.10 2006/01/06 04:47:37 cellog Exp $
+ * @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
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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 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)
+ {
+ $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' .
+ ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this)))));
+ if (PEAR::isError($roleInfo)) {
+ return $roleInfo;
+ }
+ if (!$roleInfo['locationconfig']) {
+ return false;
+ }
+ if ($roleInfo['honorsbaseinstall']) {
+ $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], $layer,
+ $pkg->getChannel());
+ if (!empty($atts['baseinstalldir'])) {
+ $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
+ }
+ } elseif ($roleInfo['unusualbaseinstall']) {
+ $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'],
+ null, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage();
+ if (!empty($atts['baseinstalldir'])) {
+ $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
+ }
+ } else {
+ $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'],
+ null, $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()
+ {
+ $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' .
+ ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this)))));
+ if (PEAR::isError($roleInfo)) {
+ return $roleInfo;
+ }
+ return $roleInfo['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()
+ {
+ $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' .
+ ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this)))));
+ if (PEAR::isError($roleInfo)) {
+ return $roleInfo;
+ }
+ return $roleInfo['executable'];
+ }
+
+ function isInstallable()
+ {
+ $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' .
+ ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this)))));
+ if (PEAR::isError($roleInfo)) {
+ return $roleInfo;
+ }
+ return $roleInfo['installable'];
+ }
+
+ function isExtension()
+ {
+ $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' .
+ ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this)))));
+ if (PEAR::isError($roleInfo)) {
+ return $roleInfo;
+ }
+ return $roleInfo['phpextension'];
+ }
+}
+?>
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role/Data.php b/trunk/PEAR/Installer/Role/Data.php
new file mode 100644
index 0000000..81362b6
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Data.php
@@ -0,0 +1,34 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Data.php,v 1.6 2006/01/06 04:47:37 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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/trunk/PEAR/Installer/Role/Data.xml b/trunk/PEAR/Installer/Role/Data.xml
new file mode 100644
index 0000000..a6294a5
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Data.xml
@@ -0,0 +1,15 @@
+
+ php
+ extsrc
+ extbin
+ zendextsrc
+ zendextbin
+ 1
+ data_dir
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role/Doc.php b/trunk/PEAR/Installer/Role/Doc.php
new file mode 100644
index 0000000..bed0405
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Doc.php
@@ -0,0 +1,34 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Doc.php,v 1.6 2006/01/06 04:47:37 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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/trunk/PEAR/Installer/Role/Doc.xml b/trunk/PEAR/Installer/Role/Doc.xml
new file mode 100644
index 0000000..16ad377
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Doc.xml
@@ -0,0 +1,15 @@
+
+ php
+ extsrc
+ extbin
+ zendextsrc
+ zendextbin
+ 1
+ doc_dir
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role/Ext.php b/trunk/PEAR/Installer/Role/Ext.php
new file mode 100644
index 0000000..f00cf7c
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Ext.php
@@ -0,0 +1,34 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Ext.php,v 1.6 2006/01/06 04:47:37 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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/trunk/PEAR/Installer/Role/Ext.xml b/trunk/PEAR/Installer/Role/Ext.xml
new file mode 100644
index 0000000..c02425c
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Ext.xml
@@ -0,0 +1,12 @@
+
+ extbin
+ zendextbin
+ 1
+ ext_dir
+ 1
+
+
+
+ 1
+
+
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role/Php.php b/trunk/PEAR/Installer/Role/Php.php
new file mode 100644
index 0000000..e8f555a
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Php.php
@@ -0,0 +1,34 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Php.php,v 1.7 2006/01/06 04:47:37 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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/trunk/PEAR/Installer/Role/Php.xml b/trunk/PEAR/Installer/Role/Php.xml
new file mode 100644
index 0000000..0cf81e4
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Php.xml
@@ -0,0 +1,15 @@
+
+ php
+ extsrc
+ extbin
+ zendextsrc
+ zendextbin
+ 1
+ php_dir
+ 1
+
+ 1
+
+
+
+
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role/Script.php b/trunk/PEAR/Installer/Role/Script.php
new file mode 100644
index 0000000..860532a
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Script.php
@@ -0,0 +1,34 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Script.php,v 1.6 2006/01/06 04:47:37 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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/trunk/PEAR/Installer/Role/Script.xml b/trunk/PEAR/Installer/Role/Script.xml
new file mode 100644
index 0000000..3a5ceeb
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Script.xml
@@ -0,0 +1,15 @@
+
+ php
+ extsrc
+ extbin
+ zendextsrc
+ zendextbin
+ 1
+ bin_dir
+ 1
+
+
+ 1
+
+
+
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role/Src.php b/trunk/PEAR/Installer/Role/Src.php
new file mode 100644
index 0000000..d85808f
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Src.php
@@ -0,0 +1,40 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Src.php,v 1.6 2006/01/06 04:47:37 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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/trunk/PEAR/Installer/Role/Src.xml b/trunk/PEAR/Installer/Role/Src.xml
new file mode 100644
index 0000000..b2e022e
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Src.xml
@@ -0,0 +1,12 @@
+
+ extsrc
+ zendextsrc
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trunk/PEAR/Installer/Role/Test.php b/trunk/PEAR/Installer/Role/Test.php
new file mode 100644
index 0000000..a944f3b
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Test.php
@@ -0,0 +1,34 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Test.php,v 1.6 2006/01/06 04:47:37 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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/trunk/PEAR/Installer/Role/Test.xml b/trunk/PEAR/Installer/Role/Test.xml
new file mode 100644
index 0000000..103680d
--- /dev/null
+++ b/trunk/PEAR/Installer/Role/Test.xml
@@ -0,0 +1,15 @@
+
+ php
+ extsrc
+ extbin
+ zendextsrc
+ zendextbin
+ 1
+ test_dir
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trunk/PEAR/PackageFile.php b/trunk/PEAR/PackageFile.php
new file mode 100644
index 0000000..4ebf0c0
--- /dev/null
+++ b/trunk/PEAR/PackageFile.php
@@ -0,0 +1,474 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: PackageFile.php,v 1.40 2006/09/25 05:12:21 cellog Exp $
+ * @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 tag does not contain a valid version
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1);
+/**
+ * Error code if the package.xml 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
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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;
+ /**
+ * Temp directory for uncompressing tgz files.
+ * @var string|false
+ */
+ var $_tmpdir;
+ var $_logger = false;
+ /**
+ * @var boolean
+ */
+ var $_rawReturn = false;
+
+ /**
+ *
+ * @param PEAR_Config $config
+ * @param ? $debug
+ * @param string @tmpdir Optional temporary directory for uncompressing
+ * files
+ */
+ function PEAR_PackageFile(&$config, $debug = false, $tmpdir = false)
+ {
+ $this->_config = $config;
+ $this->_debug = $debug;
+ $this->_tmpdir = $tmpdir;
+ }
+
+ /**
+ * 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_v1
+ */
+ 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
+ * @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;
+ } else {
+ if (isset($arr['package']['attribs']['version'])) {
+ $obj = &$this->factory($arr['package']['attribs']['version']);
+ } else {
+ $obj = &$this->factory('1.0');
+ }
+ 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('/]+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->_logger) {
+ if ($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;
+ } else {
+ if ($this->_config->get('verbose') > 0) {
+ if ($this->_logger) {
+ if ($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;
+ }
+ } elseif (preg_match('/]+version="([^"]+)"/', $data, $packageversion)) {
+ $a = PEAR::raiseError('package.xml file "' . $file .
+ '" has unsupported package.xml 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 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)) {
+ if ($this->_logger) {
+ if ($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;
+ } else {
+ $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed',
+ 2, null, null, $pf->getValidationWarnings());
+ return $a;
+ }
+ }
+ }
+
+ /**
+ * 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;
+ } else {
+ 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 (ereg('package.xml$', $name, $match)) {
+ $xml = $name;
+ break;
+ }
+ }
+ if ($this->_tmpdir) {
+ $tmpdir = $this->_tmpdir;
+ } else {
+ $tmpdir = System::mkTemp(array('-d', 'pear'));
+ 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 for extracting Archive_Tar errors
+ * @var array
+ * @access private
+ */
+ var $_extractErrors = array();
+
+ /**
+ * 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)
+ {
+ 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 == "
diff --git a/trunk/PEAR/PackageFile/Generator/v1.php b/trunk/PEAR/PackageFile/Generator/v1.php
new file mode 100644
index 0000000..c027bbc
--- /dev/null
+++ b/trunk/PEAR/PackageFile/Generator/v1.php
@@ -0,0 +1,1272 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: v1.php,v 1.72 2006/05/10 02:56:19 cellog Exp $
+ * @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
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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.5.0a1';
+ }
+
+ /**
+ * @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 = "\n";
+ $ret .= "\n";
+ $ret .= "\n" .
+" $pkginfo[package]";
+ if (isset($pkginfo['extends'])) {
+ $ret .= "\n$pkginfo[extends]";
+ }
+ $ret .=
+ "\n ".$this->_fixXmlEncoding($pkginfo['summary'])."\n" .
+" ".trim($this->_fixXmlEncoding($pkginfo['description']))."\n \n" .
+" \n";
+ foreach ($pkginfo['maintainers'] as $maint) {
+ $ret .= " \n";
+ foreach ($maint_map as $idx => $elm) {
+ $ret .= " <$elm>";
+ $ret .= $this->_fixXmlEncoding($maint[$idx]);
+ $ret .= "$elm>\n";
+ }
+ $ret .= " \n";
+ }
+ $ret .= " \n";
+ $ret .= $this->_makeReleaseXml($pkginfo, false, $state);
+ if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) {
+ $ret .= " \n";
+ foreach ($pkginfo['changelog'] as $oldrelease) {
+ $ret .= $this->_makeReleaseXml($oldrelease, true);
+ }
+ $ret .= " \n";
+ }
+ $ret .= "\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 \n";
+ if (!empty($pkginfo['version'])) {
+ $ret .= "$indent $pkginfo[version]\n";
+ }
+ if (!empty($pkginfo['release_date'])) {
+ $ret .= "$indent $pkginfo[release_date]\n";
+ }
+ if (!empty($pkginfo['release_license'])) {
+ $ret .= "$indent $pkginfo[release_license]\n";
+ }
+ if (!empty($pkginfo['release_state'])) {
+ $ret .= "$indent $pkginfo[release_state]\n";
+ }
+ if (!empty($pkginfo['release_notes'])) {
+ $ret .= "$indent ".trim($this->_fixXmlEncoding($pkginfo['release_notes']))
+ ."\n$indent \n";
+ }
+ if (!empty($pkginfo['release_warnings'])) {
+ $ret .= "$indent ".$this->_fixXmlEncoding($pkginfo['release_warnings'])."\n";
+ }
+ if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) {
+ $ret .= "$indent \n";
+ foreach ($pkginfo['release_deps'] as $dep) {
+ $ret .= "$indent _fixXmlEncoding($c['name']) . "\"";
+ if (isset($c['default'])) {
+ $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\"";
+ }
+ $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\"";
+ $ret .= "/>\n";
+ }
+ $ret .= "$indent \n";
+ }
+ if (isset($pkginfo['provides'])) {
+ foreach ($pkginfo['provides'] as $key => $what) {
+ $ret .= "$indent recursiveXmlFilelist($pkginfo['filelist']);
+ } else {
+ foreach ($pkginfo['filelist'] as $file => $fa) {
+ if (!isset($fa['role'])) {
+ $fa['role'] = '';
+ }
+ $ret .= "$indent _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 $v) {
+ $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"';
+ }
+ $ret .= "/>\n";
+ }
+ $ret .= "$indent \n";
+ }
+ }
+ }
+ $ret .= "$indent \n";
+ }
+ $ret .= "$indent \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 \n";
+ $ret .= $this->_formatDir($contents, "$indent ", $usedir);
+ $ret .= "$indent \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 _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 $v) {
+ $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"';
+ }
+ $ret .= "/>\n";
+ }
+ $ret .= "$indent \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();
+ 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:
+ *
+ *
+ * - if any install-as/platform exist, create a generic release and fill it with
+ * o tags for
+ * o tags for
+ * o tags for
+ * o tags for
+ * - create a release for each platform encountered and fill with
+ * o tags for
+ * o tags for
+ * o tags for
+ * o tags for
+ * o tags for
+ * o tags for
+ * o tags for
+ *
+ *
+ * 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 tags for
+ if (!isset($package['platform'][$file])) {
+ $generic[] = $file;
+ continue;
+ }
+ //o tags for
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file]{0} == '!') {
+ $generic[] = $file;
+ continue;
+ }
+ //o tags for
+ 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 tags for
+ $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 tags for
+ if (!isset($package['platform'][$file])) {
+ $release[$releaseNum]['filelist']['install'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ 'as' => $as,
+ ),
+ );
+ continue;
+ }
+ //o tags for
+ //
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file] == $os) {
+ $release[$releaseNum]['filelist']['install'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ 'as' => $as,
+ ),
+ );
+ continue;
+ }
+ //o tags for
+ //
+ 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 tags for
+ //
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file] == "!$os") {
+ $release[$releaseNum]['filelist']['ignore'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ ),
+ );
+ continue;
+ }
+ //o tags for
+ //
+ 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 tags for
+ if ($platform == "!$os") {
+ $release[$releaseNum]['filelist']['ignore'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ ),
+ );
+ continue;
+ }
+ //o tags for
+ 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)
+ {
+ $tests = array();
+ foreach ($deps as $name => $dep) {
+ foreach ($dep as $d) {
+ $tests[$name][] = $this->_processDep($d);
+ }
+ }
+ foreach ($tests as $name => $test) {
+ $php = array();
+ $min = array();
+ $max = 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/trunk/PEAR/PackageFile/Generator/v2.php b/trunk/PEAR/PackageFile/Generator/v2.php
new file mode 100644
index 0000000..b629015
--- /dev/null
+++ b/trunk/PEAR/PackageFile/Generator/v2.php
@@ -0,0 +1,1527 @@
+
+ * @author Stephan Schmidt (original XML_Serializer code)
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: v2.php,v 1.35 2006/03/25 21:09:08 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * file/dir manipulation routines
+ */
+require_once 'System.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
+ * @author Stephan Schmidt (original XML_Serializer code)
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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 PEAR_PackageFile_Generator_v2_PEAR_PackageFile_Generator_v2_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;
+ }
+
+ /**
+ * @return string
+ */
+ function getPackagerVersion()
+ {
+ return '1.5.0a1';
+ }
+
+ /**
+ * @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');
+ }
+ if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') &&
+ !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' .
+ ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"');
+ }
+ 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(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) &&
+ !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::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_v2::toTgz: package file object must ' .
+ 'be created from a real file');
+ }
+ // {{{ 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");
+ } else {
+ $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($this->_packagefile->getTasksNs() . ':', '', $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);
+ }
+ $filelist[$i++] = $tfile;
+ $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1);
+ $packager->log(2, "Adding file $fname");
+ }
+ }
+ }
+ // }}}
+ if ($pf1 !== null) {
+ $name = 'package2.xml';
+ } else {
+ $name = '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']);
+ }
+ 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.5.0a1';
+ 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)) {
+ if (isset($this->options['rootName'])) {
+ $tagName = $this->options['rootName'];
+ } else {
+ $tagName = 'array';
+ }
+
+ $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']);
+ }
+
+ // add doctype declaration
+ if ($this->options['addDoctype'] === true) {
+ $this->_serializedData = PEAR_PackageFile_Generator_v2_XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype'])
+ . $this->options['linebreak']
+ . $this->_serializedData;
+ }
+
+ // build xml declaration
+ if ($this->options['addDecl']) {
+ $atts = array();
+ if (isset($this->options['encoding']) ) {
+ $encoding = $this->options['encoding'];
+ } else {
+ $encoding = null;
+ }
+ $this->_serializedData = PEAR_PackageFile_Generator_v2_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 PEAR_PackageFile_Generator_v2_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 .= ' ';
+ 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) && (PEAR_PackageFile_Generator_v2_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 = PEAR_PackageFile_Generator_v2_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 .= ' ';
+ 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 {
+ $multiline = false;
+ $indent = 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')) {
+ $encoding = PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_UTF8_XML;
+ } else {
+ $encoding = PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML;
+ }
+ $tag = PEAR_PackageFile_Generator_v2_XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak'], $encoding);
+ } 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 = PEAR_PackageFile_Generator_v2_XML_Util::createTagFromArray($tag, $replaceEntities);
+ }
+ return $tag;
+ }
+}
+
+// well, it's one way to do things without extra deps ...
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 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 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. |
+// +----------------------------------------------------------------------+
+// | Authors: Stephan Schmidt |
+// +----------------------------------------------------------------------+
+//
+// $Id: v2.php,v 1.35 2006/03/25 21:09:08 cellog Exp $
+
+/**
+ * error code for invalid chars in XML name
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ERROR_INVALID_CHARS", 51);
+
+/**
+ * error code for invalid chars in XML name
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ERROR_INVALID_START", 52);
+
+/**
+ * error code for non-scalar tag content
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ERROR_NON_SCALAR_CONTENT", 60);
+
+/**
+ * error code for missing tag name
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ERROR_NO_TAG_NAME", 61);
+
+/**
+ * replace XML entities
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_REPLACE_ENTITIES", 1);
+
+/**
+ * embedd content in a CData Section
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_CDATA_SECTION", 2);
+
+/**
+ * do not replace entitites
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_NONE", 0);
+
+/**
+ * replace all XML entitites
+ * This setting will replace <, >, ", ' and &
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML", 1);
+
+/**
+ * replace only required XML entitites
+ * This setting will replace <, " and &
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML_REQUIRED", 2);
+
+/**
+ * replace HTML entitites
+ * @link http://www.php.net/htmlentities
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_HTML", 3);
+
+/**
+ * replace all XML entitites, and encode from ISO-8859-1 to UTF-8
+ * This setting will replace <, >, ", ' and &
+ */
+define("PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_UTF8_XML", 4);
+
+/**
+ * utility class for working with XML documents
+ *
+ * customized version of XML_Util 0.6.0
+ *
+ * @category XML
+ * @package PEAR
+ * @version 0.6.0
+ * @author Stephan Schmidt
+ * @author Gregory Beaver
+ */
+class PEAR_PackageFile_Generator_v2_XML_Util {
+
+ /**
+ * return API version
+ *
+ * @access public
+ * @static
+ * @return string $version API version
+ */
+ function apiVersion()
+ {
+ return "0.6";
+ }
+
+ /**
+ * replace XML entities
+ *
+ * With the optional second parameter, you may select, which
+ * entities should be replaced.
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // replace XML entites:
+ * $string = PEAR_PackageFile_Generator_v2_XML_Util::replaceEntities("This string contains < & >.");
+ *
+ *
+ * @access public
+ * @static
+ * @param string string where XML special chars should be replaced
+ * @param integer setting for entities in attribute values (one of PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML, PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML_REQUIRED, PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_HTML)
+ * @return string string with replaced chars
+ */
+ function replaceEntities($string, $replaceEntities = PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML)
+ {
+ switch ($replaceEntities) {
+ case PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_UTF8_XML:
+ return strtr(utf8_encode($string),array(
+ '&' => '&',
+ '>' => '>',
+ '<' => '<',
+ '"' => '"',
+ '\'' => ''' ));
+ break;
+ case PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML:
+ return strtr($string,array(
+ '&' => '&',
+ '>' => '>',
+ '<' => '<',
+ '"' => '"',
+ '\'' => ''' ));
+ break;
+ case PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML_REQUIRED:
+ return strtr($string,array(
+ '&' => '&',
+ '<' => '<',
+ '"' => '"' ));
+ break;
+ case PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_HTML:
+ return htmlspecialchars($string);
+ break;
+ }
+ return $string;
+ }
+
+ /**
+ * build an xml declaration
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // get an XML declaration:
+ * $xmlDecl = PEAR_PackageFile_Generator_v2_XML_Util::getXMLDeclaration("1.0", "UTF-8", true);
+ *
+ *
+ * @access public
+ * @static
+ * @param string $version xml version
+ * @param string $encoding character encoding
+ * @param boolean $standAlone document is standalone (or not)
+ * @return string $decl xml declaration
+ * @uses PEAR_PackageFile_Generator_v2_XML_Util::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("", PEAR_PackageFile_Generator_v2_XML_Util::attributesToString($attributes, false));
+ }
+
+ /**
+ * build a document type declaration
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // get a doctype declaration:
+ * $xmlDecl = PEAR_PackageFile_Generator_v2_XML_Util::getDocTypeDeclaration("rootTag","myDocType.dtd");
+ *
+ *
+ * @access public
+ * @static
+ * @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 $decl doctype declaration
+ * @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("", $root, $ref);
+ } else {
+ return sprintf("", $root, $ref, $internalDtd);
+ }
+ }
+
+ /**
+ * create string representation of an attribute list
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // build an attribute string
+ * $att = array(
+ * "foo" => "bar",
+ * "argh" => "tomato"
+ * );
+ *
+ * $attList = PEAR_PackageFile_Generator_v2_XML_Util::attributesToString($att);
+ *
+ *
+ * @access public
+ * @static
+ * @param array $attributes attribute array
+ * @param boolean|array $sort sort attribute list alphabetically, may also be an assoc array containing the keys 'sort', 'multiline', 'indent', 'linebreak' and 'entities'
+ * @param boolean $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 integer $entities setting for entities in attribute values (one of PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_NONE, PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML, PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML_REQUIRED, PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_HTML)
+ * @return string string representation of the attributes
+ * @uses PEAR_PackageFile_Generator_v2_XML_Util::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 = PEAR_PackageFile_Generator_v2_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 != PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_NONE) {
+ $value = PEAR_PackageFile_Generator_v2_XML_Util::replaceEntities($value, $entities);
+ }
+ $string .= ' '.$key.'="'.$value.'"';
+ }
+ } else {
+ $first = true;
+ foreach ($attributes as $key => $value) {
+ if ($entities != PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_NONE) {
+ $value = PEAR_PackageFile_Generator_v2_XML_Util::replaceEntities($value, $entities);
+ }
+ if ($first) {
+ $string .= " ".$key.'="'.$value.'"';
+ $first = false;
+ } else {
+ $string .= $linebreak.$indent.$key.'="'.$value.'"';
+ }
+ }
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * create a tag
+ *
+ * This method will call PEAR_PackageFile_Generator_v2_XML_Util::createTagFromArray(), which
+ * is more flexible.
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML tag:
+ * $tag = PEAR_PackageFile_Generator_v2_XML_Util::createTag("myNs:myTag", array("foo" => "bar"), "This is inside the tag", "http://www.w3c.org/myNs#");
+ *
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tagname (including namespace)
+ * @param array $attributes array containg attributes
+ * @param mixed $content
+ * @param string $namespaceUri URI of the namespace
+ * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both
+ * @param boolean $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 string $encoding encoding that should be used to translate content
+ * @return string $string XML tag
+ * @see PEAR_PackageFile_Generator_v2_XML_Util::createTagFromArray()
+ * @uses PEAR_PackageFile_Generator_v2_XML_Util::createTagFromArray() to create the tag
+ */
+ function createTag($qname, $attributes = array(), $content = null, $namespaceUri = null, $replaceEntities = PEAR_PackageFile_Generator_v2_XML_Util_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n", $encoding = PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML)
+ {
+ $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 PEAR_PackageFile_Generator_v2_XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $linebreak, $encoding);
+ }
+
+ /**
+ * create a tag from an array
+ * this method awaits an array in the following format
+ *
+ * array(
+ * "qname" => $qname // qualified name of the tag
+ * "namespace" => $namespace // namespace prefix (optional, if qname is specified or no namespace)
+ * "localpart" => $localpart, // local part of the tagname (optional, if qname is specified)
+ * "attributes" => array(), // array containing all attributes (optional)
+ * "content" => $content, // tag content (optional)
+ * "namespaceUri" => $namespaceUri // namespaceUri for the given namespace (optional)
+ * )
+ *
+ *
+ *
+ * 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 = PEAR_PackageFile_Generator_v2_XML_Util::createTagFromArray($tag);
+ *
+ *
+ * @access public
+ * @static
+ * @param array $tag tag definition
+ * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both
+ * @param boolean $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
+ * @return string $string XML tag
+ * @see PEAR_PackageFile_Generator_v2_XML_Util::createTag()
+ * @uses PEAR_PackageFile_Generator_v2_XML_Util::attributesToString() to serialize the attributes of the tag
+ * @uses PEAR_PackageFile_Generator_v2_XML_Util::splitQualifiedName() to get local part and namespace of a qualified name
+ */
+ function createTagFromArray($tag, $replaceEntities = PEAR_PackageFile_Generator_v2_XML_Util_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n", $encoding = PEAR_PackageFile_Generator_v2_XML_Util_ENTITIES_XML)
+ {
+ if (isset($tag["content"]) && !is_scalar($tag["content"])) {
+ return PEAR_PackageFile_Generator_v2_XML_Util::raiseError( "Supplied non-scalar value as tag content", PEAR_PackageFile_Generator_v2_XML_Util_ERROR_NON_SCALAR_CONTENT );
+ }
+
+ if (!isset($tag['qname']) && !isset($tag['localPart'])) {
+ return PEAR_PackageFile_Generator_v2_XML_Util::raiseError( 'You must either supply a qualified name (qname) or local tag name (localPart).', PEAR_PackageFile_Generator_v2_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();
+ }
+
+ // qualified name is not given
+ if (!isset($tag["qname"])) {
+ // check for namespace
+ if (isset($tag["namespace"]) && !empty($tag["namespace"])) {
+ $tag["qname"] = $tag["namespace"].":".$tag["localPart"];
+ } else {
+ $tag["qname"] = $tag["localPart"];
+ }
+ // namespace URI is set, but no namespace
+ } elseif (isset($tag["namespaceUri"]) && !isset($tag["namespace"])) {
+ $parts = PEAR_PackageFile_Generator_v2_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 = PEAR_PackageFile_Generator_v2_XML_Util::attributesToString($tag["attributes"], true, $multiline, $indent, $linebreak );
+ if (!isset($tag["content"]) || (string)$tag["content"] == '') {
+ $tag = sprintf("<%s%s />", $tag["qname"], $attList);
+ } else {
+ if ($replaceEntities == PEAR_PackageFile_Generator_v2_XML_Util_REPLACE_ENTITIES) {
+ $tag["content"] = PEAR_PackageFile_Generator_v2_XML_Util::replaceEntities($tag["content"], $encoding);
+ } elseif ($replaceEntities == PEAR_PackageFile_Generator_v2_XML_Util_CDATA_SECTION) {
+ $tag["content"] = PEAR_PackageFile_Generator_v2_XML_Util::createCDataSection($tag["content"]);
+ }
+ $tag = sprintf("<%s%s>%s%s>", $tag["qname"], $attList, $tag["content"], $tag["qname"] );
+ }
+ return $tag;
+ }
+
+ /**
+ * create a start element
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = PEAR_PackageFile_Generator_v2_XML_Util::createStartElement("myNs:myTag", array("foo" => "bar") ,"http://www.w3c.org/myNs#");
+ *
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tagname (including namespace)
+ * @param array $attributes array containg attributes
+ * @param string $namespaceUri URI of the namespace
+ * @param boolean $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
+ * @return string $string XML start element
+ * @see PEAR_PackageFile_Generator_v2_XML_Util::createEndElement(), PEAR_PackageFile_Generator_v2_XML_Util::createTag()
+ */
+ function createStartElement($qname, $attributes = array(), $namespaceUri = null, $multiline = false, $indent = '_auto', $linebreak = "\n")
+ {
+ // if no attributes hav been set, use empty attributes
+ if (!isset($attributes) || !is_array($attributes)) {
+ $attributes = array();
+ }
+
+ if ($namespaceUri != null) {
+ $parts = PEAR_PackageFile_Generator_v2_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 = PEAR_PackageFile_Generator_v2_XML_Util::attributesToString($attributes, true, $multiline, $indent, $linebreak);
+ $element = sprintf("<%s%s>", $qname, $attList);
+ return $element;
+ }
+
+ /**
+ * create an end element
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = PEAR_PackageFile_Generator_v2_XML_Util::createEndElement("myNs:myTag");
+ *
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tagname (including namespace)
+ * @return string $string XML end element
+ * @see PEAR_PackageFile_Generator_v2_XML_Util::createStartElement(), PEAR_PackageFile_Generator_v2_XML_Util::createTag()
+ */
+ function createEndElement($qname)
+ {
+ $element = sprintf("%s>", $qname);
+ return $element;
+ }
+
+ /**
+ * create an XML comment
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = PEAR_PackageFile_Generator_v2_XML_Util::createComment("I am a comment");
+ *
+ *
+ * @access public
+ * @static
+ * @param string $content content of the comment
+ * @return string $comment XML comment
+ */
+ function createComment($content)
+ {
+ $comment = sprintf("", $content);
+ return $comment;
+ }
+
+ /**
+ * create a CData section
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // create a CData section
+ * $tag = PEAR_PackageFile_Generator_v2_XML_Util::createCDataSection("I am content.");
+ *
+ *
+ * @access public
+ * @static
+ * @param string $data data of the CData section
+ * @return string $string CData section with content
+ */
+ function createCDataSection($data)
+ {
+ return sprintf("", $data);
+ }
+
+ /**
+ * split qualified name and return namespace and local part
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // split qualified tag
+ * $parts = PEAR_PackageFile_Generator_v2_XML_Util::splitQualifiedName("xslt:stylesheet");
+ *
+ * the returned array will contain two elements:
+ *
+ *
+ * @access public
+ * @static
+ * @param string $qname qualified tag name
+ * @param string $defaultNs default namespace (optional)
+ * @return array $parts array containing namespace and local part
+ */
+ 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
+ *
+ *
XML names are used for tagname, attribute names and various
+ * other, lesser known entities.
+ *
An XML name may only consist of alphanumeric characters,
+ * dashes, undescores and periods, and has to start with a letter
+ * or an underscore.
+ *
+ *
+ *
+ * require_once 'XML/Util.php';
+ *
+ * // verify tag name
+ * $result = PEAR_PackageFile_Generator_v2_XML_Util::isValidName("invalidTag?");
+ * if (PEAR_PackageFile_Generator_v2_XML_Util::isError($result)) {
+ * print "Invalid XML name: " . $result->getMessage();
+ * }
+ *
+ *
+ * @access public
+ * @static
+ * @param string $string string that should be checked
+ * @return mixed $valid true, if string is a valid XML name, PEAR error otherwise
+ * @todo support for other charsets
+ */
+ function isValidName($string)
+ {
+ // check for invalid chars
+ if (!preg_match("/^[[:alnum:]_\-.]$/", $string{0})) {
+ return PEAR_PackageFile_Generator_v2_XML_Util::raiseError( "XML names may only start with letter or underscore", PEAR_PackageFile_Generator_v2_XML_Util_ERROR_INVALID_START );
+ }
+
+ // check for invalid chars
+ if (!preg_match("/^([a-zA-Z_]([a-zA-Z0-9_\-\.]*)?:)?[a-zA-Z_]([a-zA-Z0-9_\-\.]+)?$/", $string)) {
+ return PEAR_PackageFile_Generator_v2_XML_Util::raiseError( "XML names may only contain alphanumeric chars, period, hyphen, colon and underscores", PEAR_PackageFile_Generator_v2_XML_Util_ERROR_INVALID_CHARS );
+ }
+ // XML name is valid
+ return true;
+ }
+
+ /**
+ * replacement for PEAR_PackageFile_Generator_v2_XML_Util::raiseError
+ *
+ * Avoids the necessity to always require
+ * PEAR.php
+ *
+ * @access public
+ * @param string error message
+ * @param integer error code
+ * @return object PEAR_Error
+ */
+ function raiseError($msg, $code)
+ {
+ require_once 'PEAR.php';
+ return PEAR::raiseError($msg, $code);
+ }
+}
+?>
\ No newline at end of file
diff --git a/trunk/PEAR/PackageFile/Parser/v1.php b/trunk/PEAR/PackageFile/Parser/v1.php
new file mode 100644
index 0000000..99391d4
--- /dev/null
+++ b/trunk/PEAR/PackageFile/Parser/v1.php
@@ -0,0 +1,461 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: v1.php,v 1.22 2006/03/27 05:25:48 cellog Exp $
+ * @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
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @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) {
+ return PEAR::raiseError('Cannot create xml parser for parsing package.xml');
+ }
+ 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);
+ return PEAR::raiseError(sprintf("XML error: %s at line %d",
+ $str = xml_error_string($code), $line), 2);
+ }
+
+ 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";
+ }
+ }
+ 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':
+ //$data = ereg_replace ('[^a-zA-Z0-9._\-]', '_', $data);
+ 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 ;-)
+ $data = $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/trunk/PEAR/PackageFile/Parser/v2.php b/trunk/PEAR/PackageFile/Parser/v2.php
new file mode 100644
index 0000000..f118b72
--- /dev/null
+++ b/trunk/PEAR/PackageFile/Parser/v2.php
@@ -0,0 +1,115 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: v2.php,v 1.19 2006/01/23 17:39:52 cellog Exp $
+ * @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
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @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";
+ }
+ }
+ 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->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/trunk/PEAR/PackageFile/v1.php b/trunk/PEAR/PackageFile/v1.php
new file mode 100644
index 0000000..713e1dc
--- /dev/null
+++ b/trunk/PEAR/PackageFile/v1.php
@@ -0,0 +1,1600 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: v1.php,v 1.71 2006/04/12 21:44:50 cellog Exp $
+ * @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 number is not present
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12);
+
+/**
+ * Error code used when a 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
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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)
+ {
+ $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 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 ($file{0} == '.' && $file{1} == '/') {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME,
+ array('file' => $file));
+ }
+ }
+ }
+ 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/trunk/PEAR/PackageFile/v2.php b/trunk/PEAR/PackageFile/v2.php
new file mode 100644
index 0000000..879d8a6
--- /dev/null
+++ b/trunk/PEAR/PackageFile/v2.php
@@ -0,0 +1,2028 @@
+
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: v2.php,v 1.134 2006/09/25 05:12:21 cellog Exp $
+ * @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
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.5.0a1
+ * @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 ');
+ }
+
+ /**
+ * WARNING - do not use this function unless you know what you're doing
+ */
+ function setRawState($state)
+ {
+ $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 and tags into a single tag with
+ * 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;
+ }
+
+ 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']);
+ $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 tag contents, if any
+ * @return array|false
+ */
+ function getUsesrole()
+ {
+ if (isset($this->_packageInfo['usesrole'])) {
+ return $this->_packageInfo['usesrole'];
+ }
+ return false;
+ }
+
+ /**
+ * Return the 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']['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)
+ {
+ $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 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 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])) {
+ 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
+ *
+ * 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);
+ $task = str_replace(' ', '/', ucwords($task));
+ $ps = (strtolower(substr(PHP_OS, 0, 3)) == 'win') ? ';' : ':';
+ foreach (explode($ps, ini_get('include_path')) as $path) {
+ if (file_exists($path . "/PEAR/Task/$task.php")) {
+ include_once "PEAR/Task/$task.php";
+ $task = str_replace('/', '_', $task);
+ if (class_exists("PEAR_Task_$task")) {
+ 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:
+ *
+ * array(
+ * tagname => array(list of tag names that follow this one),
+ * childtagname => array(list of child tag names that follow this one),
+ * )
+ *